@adonis-agora/authkit-client 1.0.0-rc.6 → 1.0.0-rc.7

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 CHANGED
@@ -1,5 +1,24 @@
1
1
  # @adonis-agora/authkit-client
2
2
 
3
+ ## 1.0.0-rc.7
4
+
5
+ ### Minor Changes
6
+
7
+ - [#495](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/495) [`4830805`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/4830805039c3f0e978c403d3062275a892704eb7) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Separate servers: every piece of an Agora deployment can run without authkit-server in its process.
8
+
9
+ - `authkitTenantVerifier({ issuer, resource, environment? })` (authkit-client) verifies service-tenant tokens on any server against the issuer's discovered JWKS and returns `{ partition, environment?, capabilities, clientId, expiresAt }` or `null`. `authkitTenantToken` now takes the token endpoint only from discovery (no guessed path).
10
+ - Service tenants get an optional `environment` (console, Admin API, SDK, `--environment`), emitted as the `environment` claim. Both verifiers take `environment` and, when it is set, accept only tokens claiming exactly that environment (fail closed); authkit-server's in-process verifier also returns `environment`. One service-tenant client gets tokens for every `urn:agora:*` resource declared.
11
+ - `registerOidcClient` announces `POST /auth/logout` as `account.signOut` on the `agora.consoles` registry (skipped when authkit-server runs in the app), so every console shows a sign-out.
12
+ - `Identity.actor` (`{ sub, email? }`) from the RFC 8693 `act` claim of an impersonated token (`deriveActor` in authkit-core).
13
+ - Org labels: authkit-server serves `GET /api/orgs/labels?ids=` (first-party confidential clients, Basic auth) and emits `org_name` for the active org; authkit-client installs a cached `Context.setTenantLabeler` (`orgLabels` option, `authkitOrgLabels`).
14
+ - Webhooks: the body carries `id` and `actorId`, `events.webhook` accepts a list; authkit-client's `registerAuthkitWebhook` / `authkitWebhookHandler` verify the HMAC, the `ts` window and the id, and re-emit on the diagnostics bus as `agora:authkit:<type>` with the in-process payload.
15
+ - `identityFromAccessToken`, `bindAuthkitIdentity` and `bridgeAuthkitToken` put a verified authkit bearer token on `ctx.authkit`.
16
+
17
+ ### Patch Changes
18
+
19
+ - Updated dependencies [[`4830805`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/4830805039c3f0e978c403d3062275a892704eb7)]:
20
+ - @adonis-agora/authkit-core@1.0.0-rc.3
21
+
3
22
  ## 1.0.0-rc.6
4
23
 
5
24
  ### Minor Changes
package/build/index.d.ts CHANGED
@@ -1,15 +1,17 @@
1
- export type { Identity } from '@adonis-agora/authkit-core';
1
+ export type { Identity, IdentityActor } 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
5
  export type { AccountExtensionsAnswer, AccountExtensionsVerifier, ServeAccountExtensionsInput, } from './src/account_extensions.js';
6
6
  export { ACCOUNT_EXTENSIONS_BINDING, ClientAccountExtensionRegistry, serveAccountExtensions, verifyAccountExtensionsToken, } from './src/account_extensions.js';
7
+ export type { AgoraConsoleSignOut, ConsolesAppLike } from './src/agora_consoles.js';
8
+ export { AGORA_CONSOLES_BINDING, announceAgoraSignOut } from './src/agora_consoles.js';
7
9
  export { Authenticator } from './src/authenticator.js';
8
10
  export type { AuthorizationRequest, CompleteAuthorizationParams, CompletedAuthorization, CreateAuthorizationRequestParams, OidcCallbackErrorCode, PendingAuthorization, } from './src/authorization.js';
9
11
  export { completeAuthorization, createAuthorizationRequest, OidcCallbackError, } from './src/authorization.js';
10
12
  export type { SessionIndex, SessionIndexEntry, ValidatedLogoutToken, ValidateLogoutTokenOptions, } from './src/backchannel_logout.js';
11
13
  export { BACKCHANNEL_LOGOUT_EVENT, InMemorySessionIndex, InvalidLogoutTokenError, validateLogoutToken, } from './src/backchannel_logout.js';
12
- export type { BackchannelLogoutCallback, BackchannelLogoutInput, ClientAccountConsoleConfig, ClientConfigInput, ResolvedClientConfig, } from './src/define_config.js';
14
+ export type { BackchannelLogoutCallback, BackchannelLogoutInput, ClientAccountConsoleConfig, ClientConfigInput, ClientOrgLabelsConfig, ResolvedClientConfig, } from './src/define_config.js';
13
15
  export { defineConfig, resolvers } from './src/define_config.js';
14
16
  export type { DiscoverOptions, OidcEndpoints } from './src/discovery.js';
15
17
  export { conventionEndpoints, discoverEndpoints, OidcDiscoveryError } from './src/discovery.js';
@@ -25,6 +27,8 @@ export type { ResiliencePolicy } from './src/http/resilient_fetch.js';
25
27
  export { resilientFetch } from './src/http/resilient_fetch.js';
26
28
  export type { IdTokenClaims, ValidateIdTokenOptions } from './src/id_token.js';
27
29
  export { InvalidIdTokenError, validateIdToken } from './src/id_token.js';
30
+ export type { AuthkitAccessTokenIdentityOptions } from './src/identity_bridge.js';
31
+ export { bindAuthkitIdentity, bridgeAuthkitToken, identityFromAccessToken, } from './src/identity_bridge.js';
28
32
  export { clearJwksCache } from './src/jwks.js';
29
33
  export type { LucidMirrorOptions } from './src/lucid_mirror.js';
30
34
  export { lucidMirror } from './src/lucid_mirror.js';
@@ -35,13 +39,19 @@ export { default as RequireOrgMiddleware } from './src/middleware/require_org_mi
35
39
  export type { ClientTenancyOptions, ContextTenant, ContextTenantLink, TenantInput, } from './src/observability/context_bridge.js';
36
40
  export type { AuthorizeParams, EndSessionParams, ExchangeParams, RefreshParams, } from './src/oidc_login.js';
37
41
  export { buildAuthorizeUrl, buildEndSessionUrl, exchangeCode, exchangeToken, generatePkce, refreshTokens, } from './src/oidc_login.js';
42
+ export type { AuthkitOrgLabelsOptions, OrgLabelResolver } from './src/org_labels.js';
43
+ export { authkitOrgLabels, installOrgLabeler, ORG_LABELS_PATH } from './src/org_labels.js';
38
44
  export type { PostLoginRedirects, RegisterOidcClientOptions, } from './src/register_oidc_client.js';
39
45
  export { registerAccountExtensionsEndpoint, registerOidcClient, } from './src/register_oidc_client.js';
46
+ export { buildIdentityFromClaims as identityFromClaims } from './src/resolvers/identity.js';
40
47
  export type { LucidRevocationStoreOptions, RevocationStore, } from './src/revocation/revocation_store.js';
41
48
  export { DEFAULT_REVOCATION_TABLE, lucidRevocationStore, } from './src/revocation/revocation_store.js';
42
49
  export { AuthkitTenantToken, type AuthkitTenantTokenFn, type AuthkitTenantTokenOptions, authkitTenantToken, TenantTokenError, } from './src/tenant_token.js';
50
+ export { AuthkitTenantVerifier, type AuthkitTenantVerifierFn, type AuthkitTenantVerifierOptions, authkitTenantVerifier, type TenantVerifier, type TenantVerifierRequest, type VerifiedTenantToken, } from './src/tenant_verifier.js';
43
51
  export type { TokenSet } from './src/types.js';
44
52
  export type { ClaimsUser, ResolveUserContext, UserinfoResolverOptions, } from './src/user_resolvers.js';
45
53
  export { createUserinfoResolver, identityToUser, } from './src/user_resolvers.js';
46
54
  export type { JwtAccessTokenClaims, VerifyJwtAccessTokenOptions, } from './src/verify_access_token.js';
47
55
  export { verifyJwtAccessToken } from './src/verify_access_token.js';
56
+ export type { AuthkitDiagnosticPayload, AuthkitWebhookEvent, AuthkitWebhookOptions, WebhookRejection, WebhookReplayStore, WebhookVerification, } from './src/webhook_receiver.js';
57
+ export { authkitWebhookHandler, emitAuthkitEvent, memoryReplayStore, registerAuthkitWebhook, verifyAuthkitWebhook, } from './src/webhook_receiver.js';
package/build/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export { configure } from './commands/configure.js';
2
2
  export { AuthkitClientManager } from './providers/authkit_client_provider.js';
3
3
  export { ACCOUNT_EXTENSIONS_BINDING, ClientAccountExtensionRegistry, serveAccountExtensions, verifyAccountExtensionsToken, } from './src/account_extensions.js';
4
+ export { AGORA_CONSOLES_BINDING, announceAgoraSignOut } from './src/agora_consoles.js';
4
5
  export { Authenticator } from './src/authenticator.js';
5
6
  export { completeAuthorization, createAuthorizationRequest, OidcCallbackError, } from './src/authorization.js';
6
7
  export { BACKCHANNEL_LOGOUT_EVENT, InMemorySessionIndex, InvalidLogoutTokenError, validateLogoutToken, } from './src/backchannel_logout.js';
@@ -12,14 +13,19 @@ export { authkitDashboardAuthorize, isAuthkitAdmin, } from './src/host/dashboard
12
13
  export { getAuthkit } from './src/host/request_authenticator.js';
13
14
  export { resilientFetch } from './src/http/resilient_fetch.js';
14
15
  export { InvalidIdTokenError, validateIdToken } from './src/id_token.js';
16
+ export { bindAuthkitIdentity, bridgeAuthkitToken, identityFromAccessToken, } from './src/identity_bridge.js';
15
17
  export { clearJwksCache } from './src/jwks.js';
16
18
  export { lucidMirror } from './src/lucid_mirror.js';
17
19
  export { default as AuthkitContextMiddleware } from './src/middleware/authkit_context_middleware.js';
18
20
  export { default as BackchannelRevocationMiddleware } from './src/middleware/backchannel_revocation_middleware.js';
19
21
  export { default as RequireOrgMiddleware } from './src/middleware/require_org_middleware.js';
20
22
  export { buildAuthorizeUrl, buildEndSessionUrl, exchangeCode, exchangeToken, generatePkce, refreshTokens, } from './src/oidc_login.js';
23
+ export { authkitOrgLabels, installOrgLabeler, ORG_LABELS_PATH } from './src/org_labels.js';
21
24
  export { registerAccountExtensionsEndpoint, registerOidcClient, } from './src/register_oidc_client.js';
25
+ export { buildIdentityFromClaims as identityFromClaims } from './src/resolvers/identity.js';
22
26
  export { DEFAULT_REVOCATION_TABLE, lucidRevocationStore, } from './src/revocation/revocation_store.js';
23
27
  export { AuthkitTenantToken, authkitTenantToken, TenantTokenError, } from './src/tenant_token.js';
28
+ export { AuthkitTenantVerifier, authkitTenantVerifier, } from './src/tenant_verifier.js';
24
29
  export { createUserinfoResolver, identityToUser, } from './src/user_resolvers.js';
25
30
  export { verifyJwtAccessToken } from './src/verify_access_token.js';
31
+ export { authkitWebhookHandler, emitAuthkitEvent, memoryReplayStore, registerAuthkitWebhook, verifyAuthkitWebhook, } from './src/webhook_receiver.js';
@@ -175,6 +175,12 @@ export declare class AuthkitClientManager {
175
175
  * is `resolvers.jwt()` (local verify, no network).
176
176
  */
177
177
  eagerTenancy(): boolean;
178
+ /**
179
+ * Registers the org labeler (see `orgLabels` in `config/authkit_client.ts`) on
180
+ * `@adonis-agora/context`. `false` when it is off, the client has no secret, or the
181
+ * context is not installed.
182
+ */
183
+ installOrgLabeler(): boolean;
178
184
  /**
179
185
  * The request's {@link Authenticator}. `options.resolver` replaces the configured
180
186
  * resolver for this authenticator only (e.g. a guard for bearer-token API routes).
@@ -187,4 +193,10 @@ export default class AuthkitClientProvider {
187
193
  protected app: ApplicationService;
188
194
  constructor(app: ApplicationService);
189
195
  register(): void;
196
+ /**
197
+ * Org labels for the consoles' org pickers, when authkit-server is not in this app (it
198
+ * serves them locally through `tenancy.label: authkitTenantLabels()`). Best effort: a
199
+ * config that cannot resolve here fails later, on first use, as before.
200
+ */
201
+ ready(): Promise<void>;
190
202
  }
@@ -14,6 +14,7 @@ import { validateIdToken, } from '../src/id_token.js';
14
14
  import { getJwks } from '../src/jwks.js';
15
15
  import { contextTenancyEnabled } from '../src/observability/context_bridge.js';
16
16
  import { exchangeToken, refreshTokens } from '../src/oidc_login.js';
17
+ import { authkitOrgLabels, installOrgLabeler } from '../src/org_labels.js';
17
18
  /** Margem (ms) antes do `expiresAt` em que o access token é renovado proativamente. */
18
19
  const REFRESH_SKEW_MS = 60_000;
19
20
  /** Aceita só o formato COM vínculo; o formato legado (TokenSet cru) vira `null`. */
@@ -509,6 +510,25 @@ export class AuthkitClientManager {
509
510
  return eager;
510
511
  return this.config.resolverFactory?.kind === 'jwt' && contextTenancyEnabled();
511
512
  }
513
+ /**
514
+ * Registers the org labeler (see `orgLabels` in `config/authkit_client.ts`) on
515
+ * `@adonis-agora/context`. `false` when it is off, the client has no secret, or the
516
+ * context is not installed.
517
+ */
518
+ installOrgLabeler() {
519
+ const cfg = this.config;
520
+ if (cfg.orgLabels === false || !cfg.clientSecret)
521
+ return false;
522
+ const options = cfg.orgLabels ?? {};
523
+ return installOrgLabeler(authkitOrgLabels({
524
+ issuer: cfg.issuer,
525
+ clientId: cfg.clientId,
526
+ clientSecret: cfg.clientSecret,
527
+ ...(options.endpoint ? { endpoint: options.endpoint } : {}),
528
+ ...(options.ttlMs !== undefined ? { ttlMs: options.ttlMs } : {}),
529
+ ...(this.deps.fetchImpl ? { fetch: this.deps.fetchImpl } : {}),
530
+ }));
531
+ }
512
532
  /**
513
533
  * The request's {@link Authenticator}. `options.resolver` replaces the configured
514
534
  * resolver for this authenticator only (e.g. a guard for bearer-token API routes).
@@ -559,6 +579,22 @@ export default class AuthkitClientProvider {
559
579
  this.app.container.singleton(ACCOUNT_EXTENSIONS_BINDING, async () => registry);
560
580
  }
561
581
  }
582
+ /**
583
+ * Org labels for the consoles' org pickers, when authkit-server is not in this app (it
584
+ * serves them locally through `tenancy.label: authkitTenantLabels()`). Best effort: a
585
+ * config that cannot resolve here fails later, on first use, as before.
586
+ */
587
+ async ready() {
588
+ if (this.app.container.hasBinding('authkit.server'))
589
+ return;
590
+ try {
591
+ const manager = await this.app.container.make(AuthkitClientManager);
592
+ manager.installOrgLabeler();
593
+ }
594
+ catch {
595
+ // no usable config yet: nothing to install
596
+ }
597
+ }
562
598
  }
563
599
  /** `sub` of a stored id_token, or undefined when there is none / it is unreadable. */
564
600
  function subjectOf(idToken) {
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The `'agora.consoles'` registry every `@adonis-agora` console reads (declared
3
+ * structurally, like in every other lib: nothing is imported). authkit-client only
4
+ * writes `account.signOut` into it, so every console on a relying-party app shows a
5
+ * working sign-out with no per-console config.
6
+ */
7
+ /** The container binding the registry lives at. */
8
+ export declare const AGORA_CONSOLES_BINDING = "agora.consoles";
9
+ /** Where every console's footer signs the viewer out. */
10
+ export type AgoraConsoleSignOut = {
11
+ action: string;
12
+ method?: 'POST';
13
+ } | {
14
+ href: string;
15
+ };
16
+ /** The slice of an AdonisJS application the announcement needs. */
17
+ export interface ConsolesAppLike {
18
+ container: {
19
+ hasBinding(binding: string): boolean;
20
+ bindValue(binding: string, value: unknown): void;
21
+ make(binding: string): Promise<unknown>;
22
+ };
23
+ }
24
+ /**
25
+ * Announces `action` (a same-origin `POST` route, e.g. `/auth/logout`) as
26
+ * `account.signOut` on the `'agora.consoles'` registry. Creates the registry when no
27
+ * console bound it yet. Keeps a sign-out something else announced first (authkit-server
28
+ * in the same app, or the app itself), skips a registry of another version, and never
29
+ * throws on one. Returns `true` when it wrote the announcement.
30
+ */
31
+ export declare function announceAgoraSignOut(app: ConsolesAppLike, action: string): Promise<boolean>;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The `'agora.consoles'` registry every `@adonis-agora` console reads (declared
3
+ * structurally, like in every other lib: nothing is imported). authkit-client only
4
+ * writes `account.signOut` into it, so every console on a relying-party app shows a
5
+ * working sign-out with no per-console config.
6
+ */
7
+ /** The container binding the registry lives at. */
8
+ export const AGORA_CONSOLES_BINDING = 'agora.consoles';
9
+ function isSameOriginPath(path) {
10
+ return (typeof path === 'string' &&
11
+ path.startsWith('/') &&
12
+ !path.startsWith('//') &&
13
+ !path.includes('\\') &&
14
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: rejecting control characters is the point
15
+ !/[\u0000-\u001f\u007f]/.test(path));
16
+ }
17
+ /**
18
+ * Announces `action` (a same-origin `POST` route, e.g. `/auth/logout`) as
19
+ * `account.signOut` on the `'agora.consoles'` registry. Creates the registry when no
20
+ * console bound it yet. Keeps a sign-out something else announced first (authkit-server
21
+ * in the same app, or the app itself), skips a registry of another version, and never
22
+ * throws on one. Returns `true` when it wrote the announcement.
23
+ */
24
+ export async function announceAgoraSignOut(app, action) {
25
+ if (!isSameOriginPath(action)) {
26
+ throw new Error(`announceAgoraSignOut: sign-out action ${JSON.stringify(action)} must be a same-origin absolute path`);
27
+ }
28
+ const container = app.container;
29
+ if (!container.hasBinding(AGORA_CONSOLES_BINDING)) {
30
+ container.bindValue(AGORA_CONSOLES_BINDING, { version: 1, entries: [] });
31
+ }
32
+ const registry = (await container.make(AGORA_CONSOLES_BINDING));
33
+ if (!registry || registry.version !== 1 || !Array.isArray(registry.entries))
34
+ return false;
35
+ const account = typeof registry.account === 'object' && registry.account !== null ? registry.account : {};
36
+ if (account.signOut !== undefined)
37
+ return false;
38
+ registry.account = { ...account, signOut: { action, method: 'POST' } };
39
+ return true;
40
+ }
@@ -1,5 +1,6 @@
1
1
  import { AUTHKIT_METRICS, NoopRecorder } from '@adonis-agora/authkit-core';
2
2
  import { populateContext } from './observability/context_bridge.js';
3
+ import { rememberOrgLabel } from './org_labels.js';
3
4
  /**
4
5
  * Autenticador do request. O parâmetro `TUser` é o tipo do usuário do app que `resolveUser`
5
6
  * devolve — o app o fixa pela interface `AuthkitUserType`, e aí `getUser()` passa a devolver
@@ -35,8 +36,10 @@ export class Authenticator {
35
36
  // Ponte estrutural: popula o contexto do Agora quando há sessão de fato
36
37
  // (best-effort, no-op sem o slot `@agora/context:set`). Apenas no caminho
37
38
  // de sucesso — não força resolução para requests anônimas.
38
- if (this.#identity)
39
+ if (this.#identity) {
39
40
  populateContext(this.#identity, this.deps.tenancy);
41
+ rememberOrgLabel(this.#identity);
42
+ }
40
43
  }
41
44
  return this.#identity;
42
45
  }
@@ -103,6 +103,21 @@ export interface ClientConfigInput {
103
103
  tenancy?: ClientTenancyOptions;
104
104
  /** Account console pages this app serves to the IdP. See {@link ClientAccountConsoleConfig}. */
105
105
  accountConsole?: ClientAccountConsoleConfig;
106
+ /**
107
+ * Organization names for every console's org picker (`Context.tenantLabels`): the active
108
+ * org from the `org_name` claim, any other from the authkit server's
109
+ * `GET /api/orgs/labels` (authenticated with `clientId`/`clientSecret`), cached.
110
+ * Installed at boot when `@adonis-agora/context` is present, the client is confidential
111
+ * and authkit-server does not run in this app. `false` turns it off. Default: on.
112
+ */
113
+ orgLabels?: false | ClientOrgLabelsConfig;
114
+ }
115
+ /** `orgLabels` in `config/authkit_client.ts`. */
116
+ export interface ClientOrgLabelsConfig {
117
+ /** The labels endpoint. Default: `/api/orgs/labels` on the issuer's origin. */
118
+ endpoint?: string;
119
+ /** How long a label is reused, in ms. Default: 5 minutes. */
120
+ ttlMs?: number;
106
121
  }
107
122
  export interface ResolvedClientConfig {
108
123
  issuer: string;
@@ -132,5 +147,7 @@ export interface ResolvedClientConfig {
132
147
  tenancy?: ClientTenancyOptions;
133
148
  /** See {@link ClientConfigInput.accountConsole}. */
134
149
  accountConsole?: ClientAccountConsoleConfig;
150
+ /** See {@link ClientConfigInput.orgLabels}. Absent means on with the defaults. */
151
+ orgLabels?: false | ClientOrgLabelsConfig;
135
152
  }
136
153
  export declare function defineConfig(config: ClientConfigInput): import("@adonisjs/core/types").ConfigProvider<ResolvedClientConfig>;
@@ -37,6 +37,7 @@ export function defineConfig(config) {
37
37
  endpoints: config.endpoints,
38
38
  tenancy: config.tenancy ?? {},
39
39
  ...(config.accountConsole ? { accountConsole: config.accountConsole } : {}),
40
+ ...(config.orgLabels !== undefined ? { orgLabels: config.orgLabels } : {}),
40
41
  };
41
42
  });
42
43
  }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Puts an identity verified somewhere else (an MCP bearer token, a remote JWT) on the
3
+ * request the way a browser session would, so `ctx.authkit` / `getAuthkit(ctx)` and an
4
+ * `authkitClientGuard` default guard see it. A console gated by `authkitDashboardAuthorize`
5
+ * then accepts the caller.
6
+ */
7
+ import type { Identity } from '@adonis-agora/authkit-core';
8
+ import type { HttpContext } from '@adonisjs/core/http';
9
+ import { type DiscoverOptions } from './discovery.js';
10
+ export interface AuthkitAccessTokenIdentityOptions {
11
+ /** Issuer URL of the authkit server. Its `jwks_uri` comes from discovery. */
12
+ issuer: string;
13
+ /** The `aud` the token must carry (the resource URL of the endpoint that received it). */
14
+ audience: string | string[];
15
+ /** Claim holding the global roles. Default: `'roles'`. */
16
+ globalRolesClaim?: string;
17
+ /** Accept a token without `typ: at+jwt`. Default: `false`. */
18
+ allowAnyTyp?: boolean;
19
+ /** Discovery options (custom `fetchImpl`, cache TTL, resilience policy). */
20
+ discovery?: Pick<DiscoverOptions, 'fetchImpl' | 'cacheTtlMs' | 'resilience' | 'overrides'>;
21
+ }
22
+ /**
23
+ * Verifies an authkit JWT access token (RFC 9068) against the issuer's discovered JWKS and
24
+ * maps it to an {@link Identity} (`userId`, `email`, `globalRoles`, org, `actor`). `null`
25
+ * for an invalid token or a `client_credentials` token (no user behind it). Throws when the
26
+ * issuer cannot be discovered.
27
+ */
28
+ export declare function identityFromAccessToken(token: string, options: AuthkitAccessTokenIdentityOptions): Promise<Identity | null>;
29
+ /**
30
+ * Binds `identity` to the request:
31
+ *
32
+ * 1. `ctx.authkit` becomes an `Authenticator` resolving to it, built by this app's authkit
33
+ * client when there is one (so `resolveUser`, e.g. `lucidMirror`, gives `getUser()` the
34
+ * app's user, and the tenant reaches `@adonis-agora/context`), else a plain one.
35
+ * 2. When `ctx.auth`'s default guard is `authkitClientGuard`, it is authenticated from it,
36
+ * so `ctx.auth.user` sees the account too.
37
+ *
38
+ * Best effort past step 1.
39
+ */
40
+ export declare function bindAuthkitIdentity(ctx: HttpContext, identity: Identity): Promise<void>;
41
+ /**
42
+ * {@link identityFromAccessToken} then {@link bindAuthkitIdentity}: the one call an MCP or
43
+ * API endpoint makes after reading a bearer token issued by a remote authkit. Returns the
44
+ * identity, or `null` (nothing bound) when the token does not verify.
45
+ */
46
+ export declare function bridgeAuthkitToken(ctx: HttpContext, token: string, options: AuthkitAccessTokenIdentityOptions): Promise<Identity | null>;
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Puts an identity verified somewhere else (an MCP bearer token, a remote JWT) on the
3
+ * request the way a browser session would, so `ctx.authkit` / `getAuthkit(ctx)` and an
4
+ * `authkitClientGuard` default guard see it. A console gated by `authkitDashboardAuthorize`
5
+ * then accepts the caller.
6
+ */
7
+ import { Authenticator } from './authenticator.js';
8
+ import { discoverEndpoints } from './discovery.js';
9
+ import { buildIdentityFromClaims } from './resolvers/identity.js';
10
+ import { verifyJwtAccessToken } from './verify_access_token.js';
11
+ /**
12
+ * Verifies an authkit JWT access token (RFC 9068) against the issuer's discovered JWKS and
13
+ * maps it to an {@link Identity} (`userId`, `email`, `globalRoles`, org, `actor`). `null`
14
+ * for an invalid token or a `client_credentials` token (no user behind it). Throws when the
15
+ * issuer cannot be discovered.
16
+ */
17
+ export async function identityFromAccessToken(token, options) {
18
+ if (typeof token !== 'string' || !token)
19
+ return null;
20
+ const endpoints = await discoverEndpoints(options.issuer, {
21
+ ...options.discovery,
22
+ onError: 'throw',
23
+ });
24
+ let claims;
25
+ try {
26
+ claims = (await verifyJwtAccessToken(token, {
27
+ issuer: endpoints.issuer,
28
+ jwksUri: endpoints.jwksUri,
29
+ audience: options.audience,
30
+ ...(options.allowAnyTyp ? { allowAnyTyp: true } : {}),
31
+ }));
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ if (typeof claims.sub !== 'string' || !claims.sub)
37
+ return null;
38
+ // An access token without an expiry is never accepted, whatever the issuer mints.
39
+ if (typeof claims.exp !== 'number')
40
+ return null;
41
+ // A client_credentials token authenticates a client, never a user.
42
+ if (typeof claims.client_id === 'string' && claims.sub === claims.client_id)
43
+ return null;
44
+ return buildIdentityFromClaims(claims, options.globalRolesClaim ?? 'roles');
45
+ }
46
+ /**
47
+ * Binds `identity` to the request:
48
+ *
49
+ * 1. `ctx.authkit` becomes an `Authenticator` resolving to it, built by this app's authkit
50
+ * client when there is one (so `resolveUser`, e.g. `lucidMirror`, gives `getUser()` the
51
+ * app's user, and the tenant reaches `@adonis-agora/context`), else a plain one.
52
+ * 2. When `ctx.auth`'s default guard is `authkitClientGuard`, it is authenticated from it,
53
+ * so `ctx.auth.user` sees the account too.
54
+ *
55
+ * Best effort past step 1.
56
+ */
57
+ export async function bindAuthkitIdentity(ctx, identity) {
58
+ const resolver = { resolve: async () => identity };
59
+ let authenticator;
60
+ try {
61
+ const container = ctx.containerResolver;
62
+ const manager = (await container?.make?.('authkit.client'));
63
+ if (manager && typeof manager.createAuthenticator === 'function') {
64
+ authenticator = await manager.createAuthenticator(ctx, {
65
+ resolver: { resolver: async () => resolver },
66
+ });
67
+ }
68
+ }
69
+ catch {
70
+ authenticator = undefined;
71
+ }
72
+ ctx.authkit =
73
+ authenticator ?? new Authenticator(ctx, { resolver });
74
+ const auth = ctx.auth;
75
+ if (auth === undefined || typeof auth.use !== 'function')
76
+ return;
77
+ try {
78
+ const guard = auth.use();
79
+ if (guard?.driverName !== 'authkit_client' || guard.isAuthenticated === true)
80
+ return;
81
+ // A silent-auth middleware may already have tried (and found no browser session).
82
+ guard.authenticationAttempted = false;
83
+ await auth.check();
84
+ }
85
+ catch {
86
+ // leave ctx.auth alone
87
+ }
88
+ }
89
+ /**
90
+ * {@link identityFromAccessToken} then {@link bindAuthkitIdentity}: the one call an MCP or
91
+ * API endpoint makes after reading a bearer token issued by a remote authkit. Returns the
92
+ * identity, or `null` (nothing bound) when the token does not verify.
93
+ */
94
+ export async function bridgeAuthkitToken(ctx, token, options) {
95
+ const identity = await identityFromAccessToken(token, options);
96
+ if (identity)
97
+ await bindAuthkitIdentity(ctx, identity);
98
+ return identity;
99
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Organization display labels on a relying-party app, for the org pickers of every
3
+ * `@adonis-agora` console (`Context.tenantLabels`). The active org's name comes from the
4
+ * session's `org_name` claim; any other id is asked to the authkit server
5
+ * (`GET /api/orgs/labels?ids=`, authenticated with this app's client id and secret) and
6
+ * cached.
7
+ */
8
+ import type { Identity } from '@adonis-agora/authkit-core';
9
+ /** `(ids, { kind? }) => { [id]: label }`, the shape `@adonis-agora/context` expects. */
10
+ export type OrgLabelResolver = (ids: readonly string[], options?: {
11
+ kind?: string;
12
+ }) => Promise<Record<string, string>>;
13
+ export interface AuthkitOrgLabelsOptions {
14
+ /** Issuer URL of the authkit server. */
15
+ issuer: string;
16
+ /** This app's client id (a confidential, first-party client of the authkit server). */
17
+ clientId: string;
18
+ /** This app's client secret. */
19
+ clientSecret: string;
20
+ /** The labels endpoint. Default: `/api/orgs/labels` on the issuer's origin. */
21
+ endpoint?: string;
22
+ /** How long a fetched label (or a miss) is reused, in ms. Default: 5 minutes. */
23
+ ttlMs?: number;
24
+ /** Abort a labels request after this many ms. Default: 5000. */
25
+ timeoutMs?: number;
26
+ /** Custom `fetch` (proxy, tests). Default: the global `fetch`. */
27
+ fetch?: typeof fetch;
28
+ }
29
+ /** Default path of authkit-server's labels endpoint. */
30
+ export declare const ORG_LABELS_PATH = "/api/orgs/labels";
31
+ /**
32
+ * Records the active org's name from a resolved session (`org_name` claim), so the
33
+ * labeler answers it with no call. Called by the `Authenticator`; best effort.
34
+ */
35
+ export declare function rememberOrgLabel(identity: Identity | null | undefined): void;
36
+ /** Clears the labels learned from claims (tests). */
37
+ export declare function __clearOrgLabelsForTests(): void;
38
+ /**
39
+ * A label resolver backed by the authkit server. Ids of another tenant kind (`kind` other
40
+ * than `'organization'`) get no label. Never throws: an unreachable server answers what is
41
+ * already known, and the ids it could not resolve show as ids.
42
+ */
43
+ export declare function authkitOrgLabels(options: AuthkitOrgLabelsOptions): OrgLabelResolver;
44
+ /**
45
+ * Registers `resolver` as the context's tenant labeler (`Context.setTenantLabeler`), so
46
+ * console org pickers show names. `tenancy.label` in `config/context.ts` still wins.
47
+ * Returns `false` (and does nothing) without `@adonis-agora/context`.
48
+ */
49
+ export declare function installOrgLabeler(resolver: OrgLabelResolver): boolean;