@adonis-agora/authkit-server 0.72.0 → 0.74.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.
Files changed (49) hide show
  1. package/README.md +4 -0
  2. package/build/commands/clients_create.d.ts +2 -0
  3. package/build/commands/clients_create.js +41 -13
  4. package/build/commands/commands.json +7 -0
  5. package/build/index.d.ts +6 -0
  6. package/build/index.js +7 -0
  7. package/build/src/commands/import_clients.js +2 -0
  8. package/build/src/host/access_token_verifier.d.ts +100 -0
  9. package/build/src/host/access_token_verifier.js +240 -0
  10. package/build/src/host/account_api/account_orgs_api_controller.js +6 -1
  11. package/build/src/host/active_org_cookie.d.ts +21 -1
  12. package/build/src/host/active_org_cookie.js +38 -0
  13. package/build/src/host/admin_api/admin_orgs_service.d.ts +7 -1
  14. package/build/src/host/admin_api/admin_orgs_service.js +11 -1
  15. package/build/src/host/admin_api/api_clients_controller.d.ts +4 -1
  16. package/build/src/host/admin_api/api_clients_controller.js +25 -2
  17. package/build/src/host/admin_api/api_orgs_controller.js +20 -20
  18. package/build/src/host/admin_api/dto.d.ts +1 -0
  19. package/build/src/host/admin_api/dto.js +1 -0
  20. package/build/src/host/admin_clients_service.d.ts +9 -0
  21. package/build/src/host/admin_clients_service.js +21 -0
  22. package/build/src/host/admin_console/console_clients_controller.d.ts +3 -1
  23. package/build/src/host/admin_console/console_clients_controller.js +25 -2
  24. package/build/src/host/admin_console/console_orgs_controller.js +1 -1
  25. package/build/src/host/admin_sessions_service.d.ts +21 -0
  26. package/build/src/host/admin_sessions_service.js +49 -0
  27. package/build/src/host/admin_validators.d.ts +11 -1
  28. package/build/src/host/admin_validators.js +36 -8
  29. package/build/src/host/bearer_account.d.ts +9 -0
  30. package/build/src/host/bearer_account.js +28 -0
  31. package/build/src/host/client_metadata.d.ts +51 -0
  32. package/build/src/host/client_metadata.js +98 -0
  33. package/build/src/host/console_session.d.ts +14 -3
  34. package/build/src/host/console_session.js +21 -9
  35. package/build/src/host/controllers/account_orgs_controller.js +3 -0
  36. package/build/src/host/controllers/interaction_controller.d.ts +18 -1
  37. package/build/src/host/controllers/interaction_controller.js +55 -14
  38. package/build/src/host/oidc_bearer_guard.d.ts +113 -0
  39. package/build/src/host/oidc_bearer_guard.js +223 -0
  40. package/build/src/host/oidc_rp_guard.d.ts +3 -1
  41. package/build/src/host/oidc_rp_guard.js +6 -2
  42. package/build/src/host/org_access_revocation.d.ts +20 -0
  43. package/build/src/host/org_access_revocation.js +41 -0
  44. package/build/src/host/ui-dist/assets/{index-Dct63ai-.js → index-CIqtoNcO.js} +1 -1
  45. package/build/src/host/ui-dist/index.html +1 -1
  46. package/build/src/provider/build_provider.js +42 -17
  47. package/build/src/provider/oidc_service.d.ts +8 -0
  48. package/build/src/provider/oidc_service.js +23 -4
  49. package/package.json +3 -3
@@ -0,0 +1,113 @@
1
+ import type { symbols } from '@adonisjs/auth';
2
+ import type { AuthClientResponse, GuardConfigProvider, GuardContract } from '@adonisjs/auth/types';
3
+ import type { SessionUserProviderContract } from '@adonisjs/auth/types/session';
4
+ import type { HttpContext } from '@adonisjs/core/http';
5
+ import type { ConfigProvider } from '@adonisjs/core/types';
6
+ import type { EmitterLike } from '@adonisjs/core/types/events';
7
+ import { type AccessTokenVerifier, type IssueAccessTokenOptions, type RemoteAccessTokenVerifierOptions, type VerifiedAccessToken } from './access_token_verifier.js';
8
+ import { type UnauthorizedAccessConstructor } from './oidc_rp_guard.js';
9
+ type RealUser<UserProvider> = UserProvider extends SessionUserProviderContract<infer U> ? U : never;
10
+ /** Política de aceitação do token, além de "é válido e não expirou". */
11
+ export interface OidcBearerGuardPolicy {
12
+ /** Escopos exigidos (TODOS). Token sem algum deles → 401 `insufficient_scope`. */
13
+ scopes?: string[];
14
+ /**
15
+ * Audience(s) aceitas (RFC 8707 resource indicators). Quando definido, o `aud`
16
+ * do token precisa conter pelo menos uma. Token opaco emitido SEM `resource`
17
+ * não tem `aud` e é recusado.
18
+ */
19
+ audience?: string | string[];
20
+ /** Allowlist de `client_id` cujos tokens são aceitos. Ausente = qualquer client. */
21
+ clientIds?: string[];
22
+ }
23
+ export type OidcBearerGuardOptions<UserProvider extends SessionUserProviderContract<unknown>> = OidcBearerGuardPolicy & {
24
+ /**
25
+ * User provider — o MESMO tipo do `oidcRpGuard` (`sessionUserProvider(...)`
26
+ * ou `authkitUserProvider()`): o `sub` do token vai para `findById`.
27
+ */
28
+ provider: UserProvider | ConfigProvider<UserProvider>;
29
+ /**
30
+ * Modo resource server REMOTO (a API não hospeda o issuer): JWT access tokens
31
+ * via JWKS do issuer (RFC 9068) e/ou tokens opacos via introspecção (RFC 7662).
32
+ * Ausente = modo IN-PROCESS: verifica contra o issuer embarcado
33
+ * (`authkit.server`), opaco e JWT.
34
+ */
35
+ remote?: RemoteAccessTokenVerifierOptions;
36
+ /** Verificador próprio (avançado). Tem precedência sobre `remote`. */
37
+ verifier?: AccessTokenVerifier;
38
+ };
39
+ export type OidcBearerGuardEvents<User> = {
40
+ 'oidc_bearer:authentication_succeeded': {
41
+ ctx: HttpContext;
42
+ guardName: string;
43
+ user: User;
44
+ token: VerifiedAccessToken;
45
+ };
46
+ 'oidc_bearer:authentication_failed': {
47
+ ctx: HttpContext;
48
+ guardName: string;
49
+ error: Error;
50
+ };
51
+ };
52
+ /**
53
+ * Guard de `@adonisjs/auth` para APIs chamadas com access token do issuer do
54
+ * authkit — o par do {@link OidcRpGuard} para clientes SEM sessão web: apps
55
+ * nativos (React Native/Expo, RFC 8252), SPAs que falam direto com a API,
56
+ * serviços. Lê `Authorization: Bearer <token>`, verifica o token (assinatura ou
57
+ * adapter, expiração, revogação), aplica a política (escopos, audience, clients)
58
+ * e resolve o user pelo `sub` no user provider.
59
+ *
60
+ * ```ts
61
+ * // config/auth.ts
62
+ * import { oidcBearerGuard, oidcRpGuard } from '@adonis-agora/authkit-server'
63
+ * import { sessionUserProvider } from '@adonisjs/auth/session'
64
+ *
65
+ * const provider = sessionUserProvider({ model: () => import('#models/user') })
66
+ *
67
+ * const authConfig = defineConfig({
68
+ * default: 'web',
69
+ * guards: {
70
+ * web: oidcRpGuard({ provider }),
71
+ * api: oidcBearerGuard({ provider, scopes: ['openid'] }),
72
+ * },
73
+ * })
74
+ *
75
+ * // start/routes.ts — aceita sessão OU bearer:
76
+ * router.get('/me', ...).use(middleware.auth({ guards: ['web', 'api'] }))
77
+ * ```
78
+ *
79
+ * Falha SEMPRE vira `E_UNAUTHORIZED_ACCESS` (401, sem redirect), com o header
80
+ * `WWW-Authenticate: Bearer …` da RFC 6750 §3.
81
+ */
82
+ export declare class OidcBearerGuard<UserProvider extends SessionUserProviderContract<unknown>> implements GuardContract<RealUser<UserProvider>> {
83
+ #private;
84
+ [symbols.GUARD_KNOWN_EVENTS]: OidcBearerGuardEvents<RealUser<UserProvider>>;
85
+ driverName: 'oidc_bearer';
86
+ authenticationAttempted: boolean;
87
+ isAuthenticated: boolean;
88
+ user?: RealUser<UserProvider>;
89
+ /** O access token verificado desta request (após `authenticate()` com sucesso). */
90
+ accessToken?: VerifiedAccessToken;
91
+ constructor(name: string, ctx: HttpContext, emitter: EmitterLike<OidcBearerGuardEvents<RealUser<UserProvider>>>, userProvider: UserProvider, verifier: AccessTokenVerifier, policy?: OidcBearerGuardPolicy, unauthorized?: UnauthorizedAccessConstructor);
92
+ getUserOrFail(): RealUser<UserProvider>;
93
+ /** `true` quando o token autenticado tem TODOS os escopos pedidos. */
94
+ hasScopes(...scopes: string[]): boolean;
95
+ authenticate(): Promise<RealUser<UserProvider>>;
96
+ /**
97
+ * `authenticate()` sem lançar — só para falha de AUTENTICAÇÃO (mesma regra do
98
+ * `OidcRpGuard#check`): erro de infraestrutura (banco, rede) é relançado.
99
+ */
100
+ check(): Promise<boolean>;
101
+ /**
102
+ * Para testes (`client.get('/api').loginAs(user)`): emite um access token REAL
103
+ * no issuer embarcado e devolve o header `Authorization`. Só no modo in-process
104
+ * — um resource server remoto não emite tokens.
105
+ */
106
+ authenticateAsClient(user: RealUser<UserProvider>, options?: IssueAccessTokenOptions): Promise<AuthClientResponse>;
107
+ }
108
+ /**
109
+ * Factory de config pro {@link OidcBearerGuard} — mesmo padrão do `oidcRpGuard()`
110
+ * e do `sessionGuard()` nativo.
111
+ */
112
+ export declare function oidcBearerGuard<UserProvider extends SessionUserProviderContract<unknown>>(config: OidcBearerGuardOptions<UserProvider>): GuardConfigProvider<(ctx: HttpContext) => OidcBearerGuard<UserProvider>>;
113
+ export {};
@@ -0,0 +1,223 @@
1
+ import { RuntimeException } from '@adonisjs/core/exceptions';
2
+ import { inProcessAccessTokenVerifier, remoteAccessTokenVerifier, } from './access_token_verifier.js';
3
+ import { clearBearerAccountId, setBearerAccountId } from './bearer_account.js';
4
+ import { cachedUnauthorizedAccessConstructor, loadUnauthorizedAccess, } from './oidc_rp_guard.js';
5
+ /**
6
+ * Driver do `E_UNAUTHORIZED_ACCESS` lançado pelo guard. `access_tokens` é o
7
+ * renderer do `@adonisjs/auth` para o guard NATIVO de access tokens: 401 com
8
+ * `{ errors: [{ message }] }` em JSON (ou texto/JSON:API) e NUNCA redirect — o que
9
+ * uma API consumida por app mobile espera.
10
+ */
11
+ const ERROR_RENDERER = 'access_tokens';
12
+ /** `Authorization: Bearer <token>` (RFC 6750 §2.1). Esquema case-insensitive. */
13
+ const BEARER = /^Bearer\s+([A-Za-z0-9\-._~+/]+=*)\s*$/i;
14
+ /**
15
+ * Guard de `@adonisjs/auth` para APIs chamadas com access token do issuer do
16
+ * authkit — o par do {@link OidcRpGuard} para clientes SEM sessão web: apps
17
+ * nativos (React Native/Expo, RFC 8252), SPAs que falam direto com a API,
18
+ * serviços. Lê `Authorization: Bearer <token>`, verifica o token (assinatura ou
19
+ * adapter, expiração, revogação), aplica a política (escopos, audience, clients)
20
+ * e resolve o user pelo `sub` no user provider.
21
+ *
22
+ * ```ts
23
+ * // config/auth.ts
24
+ * import { oidcBearerGuard, oidcRpGuard } from '@adonis-agora/authkit-server'
25
+ * import { sessionUserProvider } from '@adonisjs/auth/session'
26
+ *
27
+ * const provider = sessionUserProvider({ model: () => import('#models/user') })
28
+ *
29
+ * const authConfig = defineConfig({
30
+ * default: 'web',
31
+ * guards: {
32
+ * web: oidcRpGuard({ provider }),
33
+ * api: oidcBearerGuard({ provider, scopes: ['openid'] }),
34
+ * },
35
+ * })
36
+ *
37
+ * // start/routes.ts — aceita sessão OU bearer:
38
+ * router.get('/me', ...).use(middleware.auth({ guards: ['web', 'api'] }))
39
+ * ```
40
+ *
41
+ * Falha SEMPRE vira `E_UNAUTHORIZED_ACCESS` (401, sem redirect), com o header
42
+ * `WWW-Authenticate: Bearer …` da RFC 6750 §3.
43
+ */
44
+ export class OidcBearerGuard {
45
+ #name;
46
+ #ctx;
47
+ #emitter;
48
+ #userProvider;
49
+ #verifier;
50
+ #policy;
51
+ #unauthorized;
52
+ driverName = 'oidc_bearer';
53
+ authenticationAttempted = false;
54
+ isAuthenticated = false;
55
+ user;
56
+ /** O access token verificado desta request (após `authenticate()` com sucesso). */
57
+ accessToken;
58
+ constructor(name, ctx, emitter, userProvider, verifier, policy = {}, unauthorized) {
59
+ this.#name = name;
60
+ this.#ctx = ctx;
61
+ this.#emitter = emitter;
62
+ this.#userProvider = userProvider;
63
+ this.#verifier = verifier;
64
+ this.#policy = policy;
65
+ this.#unauthorized = unauthorized ?? cachedUnauthorizedAccessConstructor();
66
+ }
67
+ #unauthorizedError(message) {
68
+ const Unauthorized = this.#unauthorized ?? cachedUnauthorizedAccessConstructor();
69
+ if (!Unauthorized)
70
+ return new RuntimeException(message);
71
+ return new Unauthorized(message, { guardDriverName: ERROR_RENDERER });
72
+ }
73
+ /**
74
+ * Falha de autenticação: header `WWW-Authenticate` (RFC 6750 §3), evento, e o
75
+ * `E_UNAUTHORIZED_ACCESS`. Sem token → só o desafio `Bearer`; token recusado →
76
+ * `error="invalid_token"` / `"insufficient_scope"`.
77
+ */
78
+ #fail(bearerError) {
79
+ let challenge = 'Bearer';
80
+ if (bearerError)
81
+ challenge += ` error="${bearerError}"`;
82
+ if (bearerError === 'insufficient_scope' && this.#policy.scopes?.length) {
83
+ challenge += `, scope="${this.#policy.scopes.join(' ')}"`;
84
+ }
85
+ this.#ctx.response?.header?.('WWW-Authenticate', challenge);
86
+ clearBearerAccountId(this.#ctx);
87
+ const error = this.#unauthorizedError('Unauthorized access');
88
+ this.#emitter.emit('oidc_bearer:authentication_failed', {
89
+ ctx: this.#ctx,
90
+ guardName: this.#name,
91
+ error,
92
+ });
93
+ return error;
94
+ }
95
+ getUserOrFail() {
96
+ if (!this.user) {
97
+ throw this.#unauthorizedError('Cannot access user. Authentication has not been attempted or failed.');
98
+ }
99
+ return this.user;
100
+ }
101
+ /** `true` quando o token autenticado tem TODOS os escopos pedidos. */
102
+ hasScopes(...scopes) {
103
+ const granted = new Set(this.accessToken?.scopes ?? []);
104
+ return scopes.every((s) => granted.has(s));
105
+ }
106
+ #bearerToken() {
107
+ const header = this.#ctx.request.header('authorization');
108
+ if (!header)
109
+ return undefined;
110
+ return BEARER.exec(header)?.[1];
111
+ }
112
+ #policyError(token) {
113
+ const { clientIds, audience, scopes } = this.#policy;
114
+ if (clientIds && (!token.clientId || !clientIds.includes(token.clientId))) {
115
+ return 'invalid_token';
116
+ }
117
+ if (audience !== undefined) {
118
+ const accepted = Array.isArray(audience) ? audience : [audience];
119
+ if (!token.audience.some((a) => accepted.includes(a)))
120
+ return 'invalid_token';
121
+ }
122
+ if (scopes?.length) {
123
+ const granted = new Set(token.scopes);
124
+ if (!scopes.every((s) => granted.has(s)))
125
+ return 'insufficient_scope';
126
+ }
127
+ return null;
128
+ }
129
+ async authenticate() {
130
+ if (this.authenticationAttempted) {
131
+ return this.getUserOrFail();
132
+ }
133
+ this.authenticationAttempted = true;
134
+ this.#unauthorized ??= await loadUnauthorizedAccess('oidcBearerGuard');
135
+ const raw = this.#bearerToken();
136
+ if (!raw)
137
+ throw this.#fail();
138
+ const token = await this.#verifier.verify(raw);
139
+ if (!token)
140
+ throw this.#fail('invalid_token');
141
+ const policyError = this.#policyError(token);
142
+ if (policyError)
143
+ throw this.#fail(policyError);
144
+ const guardUser = await this.#userProvider.findById(token.sub);
145
+ if (!guardUser)
146
+ throw this.#fail('invalid_token');
147
+ this.user = guardUser.getOriginal();
148
+ this.accessToken = token;
149
+ this.isAuthenticated = true;
150
+ setBearerAccountId(this.#ctx, String(guardUser.getId()));
151
+ this.#emitter.emit('oidc_bearer:authentication_succeeded', {
152
+ ctx: this.#ctx,
153
+ guardName: this.#name,
154
+ user: this.user,
155
+ token,
156
+ });
157
+ return this.user;
158
+ }
159
+ /**
160
+ * `authenticate()` sem lançar — só para falha de AUTENTICAÇÃO (mesma regra do
161
+ * `OidcRpGuard#check`): erro de infraestrutura (banco, rede) é relançado.
162
+ */
163
+ async check() {
164
+ try {
165
+ await this.authenticate();
166
+ return true;
167
+ }
168
+ catch (error) {
169
+ const Unauthorized = this.#unauthorized ?? cachedUnauthorizedAccessConstructor();
170
+ if (Unauthorized && error instanceof Unauthorized)
171
+ return false;
172
+ throw error;
173
+ }
174
+ }
175
+ /**
176
+ * Para testes (`client.get('/api').loginAs(user)`): emite um access token REAL
177
+ * no issuer embarcado e devolve o header `Authorization`. Só no modo in-process
178
+ * — um resource server remoto não emite tokens.
179
+ */
180
+ async authenticateAsClient(user, options) {
181
+ if (!this.#verifier.issue) {
182
+ throw new RuntimeException('oidcBearerGuard: authenticateAsClient só funciona no modo in-process (o app hospeda o issuer).');
183
+ }
184
+ const guardUser = await this.#userProvider.createUserForGuard(user);
185
+ const token = await this.#verifier.issue(String(guardUser.getId()), {
186
+ ...options,
187
+ scopes: options?.scopes ?? this.#policy.scopes,
188
+ });
189
+ return { headers: { authorization: `Bearer ${token}` } };
190
+ }
191
+ }
192
+ /**
193
+ * Factory de config pro {@link OidcBearerGuard} — mesmo padrão do `oidcRpGuard()`
194
+ * e do `sessionGuard()` nativo.
195
+ */
196
+ export function oidcBearerGuard(config) {
197
+ return {
198
+ async resolver(name, app) {
199
+ const emitter = await app.container.make('emitter');
200
+ const unauthorized = await loadUnauthorizedAccess('oidcBearerGuard');
201
+ let userProvider;
202
+ if (typeof config.provider.resolver === 'function') {
203
+ userProvider = await config.provider.resolver(app);
204
+ }
205
+ else {
206
+ userProvider = config.provider;
207
+ }
208
+ // In-process: o `authkit.server` é resolvido na PRIMEIRA verificação (não
209
+ // aqui) — a config do auth pode ser resolvida antes do provider do authkit
210
+ // terminar o boot.
211
+ const verifier = config.verifier ??
212
+ (config.remote
213
+ ? remoteAccessTokenVerifier(config.remote)
214
+ : inProcessAccessTokenVerifier(async () => (await app.container.make('authkit.server'))));
215
+ const policy = {
216
+ scopes: config.scopes,
217
+ audience: config.audience,
218
+ clientIds: config.clientIds,
219
+ };
220
+ return (ctx) => new OidcBearerGuard(name, ctx, emitter, userProvider, verifier, policy, unauthorized);
221
+ },
222
+ };
223
+ }
@@ -15,13 +15,15 @@ export type UnauthorizedAccessConstructor = new (message: string, options: {
15
15
  guardDriverName: string;
16
16
  redirectTo?: string;
17
17
  }) => Error;
18
+ /** O construtor já resolvido por {@link loadUnauthorizedAccess}, se houver. */
19
+ export declare function cachedUnauthorizedAccessConstructor(): UnauthorizedAccessConstructor | undefined;
18
20
  /**
19
21
  * Captura o `E_UNAUTHORIZED_ACCESS` real do `@adonisjs/auth` sem import
20
22
  * estático. Chamado no boot pelo {@link oidcRpGuard} (falha cedo e com uma
21
23
  * mensagem útil se o peer não estiver instalado) e, como rede de segurança, na
22
24
  * primeira `authenticate()` de um guard construído à mão.
23
25
  */
24
- export declare function loadUnauthorizedAccess(): Promise<UnauthorizedAccessConstructor>;
26
+ export declare function loadUnauthorizedAccess(guardFactoryName?: string): Promise<UnauthorizedAccessConstructor>;
25
27
  export type OidcRpGuardOptions<UserProvider extends SessionUserProviderContract<unknown>> = {
26
28
  provider: UserProvider | ConfigProvider<UserProvider>;
27
29
  sessionKey?: string;
@@ -2,13 +2,17 @@ import { RuntimeException } from '@adonisjs/core/exceptions';
2
2
  import { ACCOUNT_SESSION_KEY } from './account_session_key.js';
3
3
  /** Memo do construtor entre instâncias — o `import()` só paga o custo uma vez. */
4
4
  let cachedUnauthorizedAccess;
5
+ /** O construtor já resolvido por {@link loadUnauthorizedAccess}, se houver. */
6
+ export function cachedUnauthorizedAccessConstructor() {
7
+ return cachedUnauthorizedAccess;
8
+ }
5
9
  /**
6
10
  * Captura o `E_UNAUTHORIZED_ACCESS` real do `@adonisjs/auth` sem import
7
11
  * estático. Chamado no boot pelo {@link oidcRpGuard} (falha cedo e com uma
8
12
  * mensagem útil se o peer não estiver instalado) e, como rede de segurança, na
9
13
  * primeira `authenticate()` de um guard construído à mão.
10
14
  */
11
- export async function loadUnauthorizedAccess() {
15
+ export async function loadUnauthorizedAccess(guardFactoryName = 'oidcRpGuard') {
12
16
  if (cachedUnauthorizedAccess)
13
17
  return cachedUnauthorizedAccess;
14
18
  try {
@@ -17,7 +21,7 @@ export async function loadUnauthorizedAccess() {
17
21
  return cachedUnauthorizedAccess;
18
22
  }
19
23
  catch (error) {
20
- throw new RuntimeException('oidcRpGuard() precisa de "@adonisjs/auth" instalado (é um peer opcional do ' +
24
+ throw new RuntimeException(`${guardFactoryName}() precisa de "@adonisjs/auth" instalado (é um peer opcional do ` +
21
25
  '@adonis-agora/authkit-server, só necessário se você plugar este guard em config/auth.ts). ' +
22
26
  'Rode `npm i @adonisjs/auth` (ou pnpm/yarn).', { cause: error });
23
27
  }
@@ -0,0 +1,20 @@
1
+ import type { OidcService } from '../provider/oidc_service.js';
2
+ import { type RevokeResult } from './admin_sessions_service.js';
3
+ /**
4
+ * Derruba os grants (e os tokens deles) que carregam a org `orgId` — de uma conta
5
+ * (membro removido / saiu da org) ou de todas (org apagada, `accountId` omitido).
6
+ *
7
+ * Chamado DEPOIS que o store confirmou a remoção. Os quatro caminhos que removem
8
+ * membro (form `/account/orgs`, `/account/api/orgs`, console admin, Admin API) e o
9
+ * `deleteOrg` passam por aqui, para que a regra seja uma só.
10
+ *
11
+ * Best-effort, e nunca silencioso: a membership JÁ saiu do store, então falhar a
12
+ * request aqui só mentiria para quem removeu ("deu erro", mas o membro saiu). A
13
+ * conferência da emissão (`verifyActiveOrgMembership`) continua sendo a rede de
14
+ * segurança — no pior caso, o claim cai no próximo refresh. A falha é logada em
15
+ * `error`, porque uma revogação que não aconteceu precisa aparecer.
16
+ *
17
+ * `service` sem `config.AdapterClass` (um ctx de teste que só carrega a config)
18
+ * não tem adapter OIDC onde revogar: devolve `null` sem tentar.
19
+ */
20
+ export declare function revokeOrgAccess(service: Pick<OidcService, 'config'> | undefined, orgId: string, accountId?: string): Promise<RevokeResult | null>;
@@ -0,0 +1,41 @@
1
+ import { getBootedApp } from '../../services/booted_app.js';
2
+ import { AdminSessionsService } from './admin_sessions_service.js';
3
+ /**
4
+ * Derruba os grants (e os tokens deles) que carregam a org `orgId` — de uma conta
5
+ * (membro removido / saiu da org) ou de todas (org apagada, `accountId` omitido).
6
+ *
7
+ * Chamado DEPOIS que o store confirmou a remoção. Os quatro caminhos que removem
8
+ * membro (form `/account/orgs`, `/account/api/orgs`, console admin, Admin API) e o
9
+ * `deleteOrg` passam por aqui, para que a regra seja uma só.
10
+ *
11
+ * Best-effort, e nunca silencioso: a membership JÁ saiu do store, então falhar a
12
+ * request aqui só mentiria para quem removeu ("deu erro", mas o membro saiu). A
13
+ * conferência da emissão (`verifyActiveOrgMembership`) continua sendo a rede de
14
+ * segurança — no pior caso, o claim cai no próximo refresh. A falha é logada em
15
+ * `error`, porque uma revogação que não aconteceu precisa aparecer.
16
+ *
17
+ * `service` sem `config.AdapterClass` (um ctx de teste que só carrega a config)
18
+ * não tem adapter OIDC onde revogar: devolve `null` sem tentar.
19
+ */
20
+ export async function revokeOrgAccess(service, orgId, accountId) {
21
+ if (!service?.config?.AdapterClass)
22
+ return null;
23
+ try {
24
+ return await new AdminSessionsService(service).revokeOrgGrants(orgId, accountId);
25
+ }
26
+ catch (error) {
27
+ await logFailure(error, orgId, accountId);
28
+ return null;
29
+ }
30
+ }
31
+ async function logFailure(error, orgId, accountId) {
32
+ const msg = 'authkit: falha ao revogar os grants da org — o claim de org cai só no próximo refresh';
33
+ try {
34
+ const logger = await getBootedApp().container.make('logger');
35
+ logger.error({ err: error, orgId, accountId }, msg);
36
+ }
37
+ catch {
38
+ // eslint-disable-next-line no-console
39
+ console.error(msg, { orgId, accountId, error });
40
+ }
41
+ }