@adonis-agora/authkit-server 0.42.0 → 0.43.0

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.
@@ -561,6 +561,21 @@ export interface AuthServerConfigInput {
561
561
  ttl?: TtlConfig;
562
562
  /** Nome da CLAIM (não do scope) onde os papéis globais são emitidos. Default: 'roles'. */
563
563
  globalRolesClaim?: string;
564
+ /**
565
+ * Resolves the global-roles claim at token-mint time. Return the roles to embed
566
+ * in the `<globalRolesClaim>` claim for a first-party client. Lets the host source
567
+ * roles from an external authority (e.g. @adonis-agora/authz) or a custom store
568
+ * instead of the account's stored `globalRoles`. Default when omitted:
569
+ * `account.globalRoles ?? []` (unchanged behavior).
570
+ */
571
+ resolveTokenRoles?: (account: AuthAccount, context: {
572
+ clientId?: string;
573
+ activeOrg?: {
574
+ orgId: string;
575
+ orgSlug: string;
576
+ orgRole: string;
577
+ } | null;
578
+ }) => string[] | Promise<string[]>;
564
579
  cookieKeys?: string[];
565
580
  observability?: ObservabilityConfig;
566
581
  /** Contrato primário de identidade. Deriva findAccount/verifyCredentials do provider. */
@@ -769,6 +784,18 @@ export interface ResolvedServerConfig {
769
784
  session: number;
770
785
  };
771
786
  globalRolesClaim: string;
787
+ /**
788
+ * Resolves the global-roles claim at token-mint time (first-party only). When
789
+ * omitted, the mint falls back to `account.globalRoles ?? []` (unchanged behavior).
790
+ */
791
+ resolveTokenRoles?: (account: AuthAccount, context: {
792
+ clientId?: string;
793
+ activeOrg?: {
794
+ orgId: string;
795
+ orgSlug: string;
796
+ orgRole: string;
797
+ } | null;
798
+ }) => string[] | Promise<string[]>;
772
799
  cookieKeys: string[];
773
800
  observability: ObservabilityConfig;
774
801
  findAccount: (sub: string) => Promise<AuthAccount | null>;
@@ -303,6 +303,7 @@ export function defineConfig(config) {
303
303
  session: toSeconds(config.ttl?.session, 604800),
304
304
  },
305
305
  globalRolesClaim: config.globalRolesClaim ?? "roles",
306
+ resolveTokenRoles: config.resolveTokenRoles,
306
307
  cookieKeys: config.cookieKeys ?? [],
307
308
  observability: config.observability ?? {},
308
309
  findAccount: (sub) => config.accountStore.findById(sub),
@@ -98,7 +98,9 @@ export class OidcService {
98
98
  };
99
99
  // roles/org_* são dados de autorização interna: só para first-party.
100
100
  if (firstParty) {
101
- base[config.globalRolesClaim] = user.globalRoles ?? [];
101
+ base[config.globalRolesClaim] = config.resolveTokenRoles
102
+ ? await config.resolveTokenRoles(user, { clientId, activeOrg })
103
+ : (user.globalRoles ?? []);
102
104
  // Emite claims de org somente quando há uma org ativa na sessão.
103
105
  if (activeOrg) {
104
106
  base['org_id'] = activeOrg.orgId;
@@ -115,6 +117,7 @@ export class OidcService {
115
117
  registerTokenExchange(provider, {
116
118
  findAccount: config.findAccount,
117
119
  globalRolesClaim: config.globalRolesClaim,
120
+ resolveTokenRoles: config.resolveTokenRoles,
118
121
  // Resource indicators (RFC 8707) suportados: o `audience` default + cada
119
122
  // resource declarado. Usado para validar `audience`/`resource` no pedido de
120
123
  // token-exchange — alvos fora desta lista são rejeitados (invalid_target).
@@ -1,4 +1,5 @@
1
1
  import type { AuditSink } from '../audit/audit_sink.js';
2
+ import type { AuthAccount } from '../accounts/account_store.js';
2
3
  export interface TokenExchangeAccount {
3
4
  id: string;
4
5
  email?: string;
@@ -8,6 +9,20 @@ export interface TokenExchangeAccount {
8
9
  export interface TokenExchangeDeps {
9
10
  findAccount: (sub: string) => Promise<TokenExchangeAccount | null>;
10
11
  globalRolesClaim: string;
12
+ /**
13
+ * Resolves the global-roles claim at token-mint time. When omitted, falls back to
14
+ * `target.globalRoles ?? []` (unchanged behavior). Mirrors the mint-time hook used
15
+ * by the authorization-code flow so impersonated tokens source roles from the same
16
+ * authority (e.g. @adonis-agora/authz) or custom store.
17
+ */
18
+ resolveTokenRoles?: (account: AuthAccount, context: {
19
+ clientId?: string;
20
+ activeOrg?: {
21
+ orgId: string;
22
+ orgSlug: string;
23
+ orgRole: string;
24
+ } | null;
25
+ }) => string[] | Promise<string[]>;
11
26
  adminRole?: string;
12
27
  /**
13
28
  * Resource indicators (RFC 8707) suportados pelo provider. Quando o pedido traz
@@ -82,12 +82,20 @@ export function registerTokenExchange(provider, deps) {
82
82
  }
83
83
  const at = new provider.AccessToken({ accountId: target.id, client, scope });
84
84
  const accessToken = await at.save();
85
+ // Token exchange is not tied to a browser session, so there is no active org
86
+ // context here — roles are resolved for the impersonated target with clientId only.
87
+ const roles = deps.resolveTokenRoles
88
+ ? await deps.resolveTokenRoles(target, {
89
+ clientId: client?.clientId,
90
+ activeOrg: null,
91
+ })
92
+ : (target.globalRoles ?? []);
85
93
  const idToken = new provider.IdToken({
86
94
  sub: target.id,
87
95
  email: target.email,
88
96
  email_verified: true,
89
97
  name: target.name,
90
- [deps.globalRolesClaim]: target.globalRoles ?? [],
98
+ [deps.globalRolesClaim]: roles,
91
99
  }, { ctx });
92
100
  idToken.scope = scope;
93
101
  idToken.set('act', { sub: actor.id });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adonis-agora/authkit-server",
3
- "version": "0.42.0",
3
+ "version": "0.43.0",
4
4
  "description": "AdonisJS OIDC/OAuth2 provider (Identity Provider) toolkit: ejectable auth server with sessions, rate-limiting, MFA/TOTP, audit log, federated logout and OpenTelemetry metrics.",
5
5
  "license": "MIT",
6
6
  "author": "dudousxd",