@adonis-agora/authkit-client 0.14.0 → 0.15.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 (36) hide show
  1. package/build/commands/configure.js +5 -3
  2. package/build/index.d.ts +32 -28
  3. package/build/index.js +18 -15
  4. package/build/providers/authkit_client_provider.d.ts +6 -6
  5. package/build/providers/authkit_client_provider.js +19 -15
  6. package/build/services/main.d.ts +1 -1
  7. package/build/services/main.js +2 -2
  8. package/build/src/authenticator.d.ts +3 -3
  9. package/build/src/authenticator.js +4 -4
  10. package/build/src/define_config.d.ts +5 -5
  11. package/build/src/define_config.js +5 -5
  12. package/build/src/discovery.d.ts +1 -1
  13. package/build/src/discovery.js +5 -5
  14. package/build/src/dpop.js +1 -1
  15. package/build/src/host/authkit_client_guard.d.ts +100 -0
  16. package/build/src/host/authkit_client_guard.js +153 -0
  17. package/build/src/host/request_authenticator.d.ts +31 -0
  18. package/build/src/host/request_authenticator.js +24 -0
  19. package/build/src/middleware/authkit_context_middleware.d.ts +28 -0
  20. package/build/src/middleware/authkit_context_middleware.js +30 -0
  21. package/build/src/middleware/backchannel_revocation_middleware.js +0 -1
  22. package/build/src/observability/context_bridge.d.ts +1 -1
  23. package/build/src/observability/context_bridge.js +8 -8
  24. package/build/src/oidc_login.d.ts +2 -2
  25. package/build/src/oidc_login.js +29 -31
  26. package/build/src/register_oidc_client.d.ts +1 -1
  27. package/build/src/register_oidc_client.js +1 -2
  28. package/build/src/resolvers/factory.js +2 -2
  29. package/build/src/resolvers/jwt_resolver.d.ts +1 -1
  30. package/build/src/resolvers/jwt_resolver.js +12 -1
  31. package/build/src/resolvers/opaque_resolver.d.ts +1 -1
  32. package/build/src/resolvers/pat_resolver.d.ts +1 -1
  33. package/build/src/revocation/revocation_store.js +0 -0
  34. package/build/types.d.ts +1 -1
  35. package/package.json +10 -3
  36. package/stubs/main.ts +2 -2
@@ -4,13 +4,15 @@ export async function configure(command) {
4
4
  await codemods.makeUsingStub(stubsRoot, 'config/authkit_client.stub', {});
5
5
  await codemods.makeUsingStub(stubsRoot, 'controllers/oidc_session_controller.stub', {});
6
6
  await codemods.updateRcFile((rc) => rc.addProvider('@adonis-agora/authkit-client/authkit_client_provider'));
7
- await codemods.registerMiddleware('router', [{ path: '@adonis-agora/authkit-client/authkit_middleware' }]);
7
+ await codemods.registerMiddleware('router', [
8
+ { path: '@adonis-agora/authkit-client/authkit_middleware' },
9
+ ]);
8
10
  await codemods.defineEnvValidations({
9
11
  leadingComment: 'Variáveis do @adonis-agora/authkit-client (OIDC client)',
10
12
  variables: {
11
13
  AUTHKIT_ISSUER: `Env.schema.string({ format: 'url', tld: false })`,
12
- AUTHKIT_CLIENT_ID: `Env.schema.string()`,
13
- AUTHKIT_CLIENT_SECRET: `Env.schema.string.optional()`,
14
+ AUTHKIT_CLIENT_ID: 'Env.schema.string()',
15
+ AUTHKIT_CLIENT_SECRET: 'Env.schema.string.optional()',
14
16
  AUTHKIT_REDIRECT_URI: `Env.schema.string({ format: 'url', tld: false })`,
15
17
  },
16
18
  });
package/build/index.d.ts CHANGED
@@ -1,28 +1,32 @@
1
- export { defineConfig, resolvers } from "./src/define_config.js";
2
- export { Authenticator } from "./src/authenticator.js";
3
- export { identityToUser, createUserinfoResolver, } from "./src/user_resolvers.js";
4
- export type { ClaimsUser, ResolveUserContext, UserinfoResolverOptions, } from "./src/user_resolvers.js";
5
- export { buildAuthorizeUrl, buildEndSessionUrl, exchangeCode, exchangeToken, refreshTokens, generatePkce, } from "./src/oidc_login.js";
6
- export type { EndSessionParams, RefreshParams } from "./src/oidc_login.js";
7
- export { discoverEndpoints, conventionEndpoints } from "./src/discovery.js";
8
- export type { OidcEndpoints, DiscoverOptions } from "./src/discovery.js";
9
- export { resilientFetch } from "./src/http/resilient_fetch.js";
10
- export type { ResiliencePolicy } from "./src/http/resilient_fetch.js";
11
- export { AuthkitClientManager } from "./providers/authkit_client_provider.js";
12
- export type { ResolvedClientConfig, ClientConfigInput, BackchannelLogoutCallback, BackchannelLogoutInput, } from "./src/define_config.js";
13
- export { lucidRevocationStore, DEFAULT_REVOCATION_TABLE, } from "./src/revocation/revocation_store.js";
14
- export type { RevocationStore, LucidRevocationStoreOptions, } from "./src/revocation/revocation_store.js";
15
- export { default as BackchannelRevocationMiddleware } from "./src/middleware/backchannel_revocation_middleware.js";
16
- export { lucidMirror } from "./src/lucid_mirror.js";
17
- export type { LucidMirrorOptions } from "./src/lucid_mirror.js";
18
- export { registerOidcClient } from "./src/register_oidc_client.js";
19
- export type { RegisterOidcClientOptions, PostLoginRedirects, } from "./src/register_oidc_client.js";
20
- export { validateLogoutToken, InvalidLogoutTokenError, InMemorySessionIndex, BACKCHANNEL_LOGOUT_EVENT, } from "./src/backchannel_logout.js";
21
- export type { ValidateLogoutTokenOptions, ValidatedLogoutToken, SessionIndex, SessionIndexEntry, } from "./src/backchannel_logout.js";
22
- export { generateDpopKeyPair, createDpopProof, dpopJwkThumbprint, } from "./src/dpop.js";
23
- export type { DpopKeyPair, CreateDpopProofInput } from "./src/dpop.js";
24
- export { verifyJwtAccessToken, clearJwksCache, } from "./src/verify_access_token.js";
25
- export type { VerifyJwtAccessTokenOptions, JwtAccessTokenClaims, } from "./src/verify_access_token.js";
26
- export type { TokenSet } from "./src/types.js";
27
- export type { Identity } from "@adonis-agora/authkit-core";
28
- export { configure } from "./commands/configure.js";
1
+ export { defineConfig, resolvers } from './src/define_config.js';
2
+ export { Authenticator } from './src/authenticator.js';
3
+ export { identityToUser, createUserinfoResolver, } from './src/user_resolvers.js';
4
+ export type { ClaimsUser, ResolveUserContext, UserinfoResolverOptions, } from './src/user_resolvers.js';
5
+ export { buildAuthorizeUrl, buildEndSessionUrl, exchangeCode, exchangeToken, refreshTokens, generatePkce, } from './src/oidc_login.js';
6
+ export type { EndSessionParams, RefreshParams } from './src/oidc_login.js';
7
+ export { discoverEndpoints, conventionEndpoints } from './src/discovery.js';
8
+ export type { OidcEndpoints, DiscoverOptions } from './src/discovery.js';
9
+ export { resilientFetch } from './src/http/resilient_fetch.js';
10
+ export type { ResiliencePolicy } from './src/http/resilient_fetch.js';
11
+ export { AuthkitClientManager } from './providers/authkit_client_provider.js';
12
+ export type { ResolvedClientConfig, ClientConfigInput, BackchannelLogoutCallback, BackchannelLogoutInput, } from './src/define_config.js';
13
+ export { lucidRevocationStore, DEFAULT_REVOCATION_TABLE, } from './src/revocation/revocation_store.js';
14
+ export type { RevocationStore, LucidRevocationStoreOptions, } from './src/revocation/revocation_store.js';
15
+ export { default as BackchannelRevocationMiddleware } from './src/middleware/backchannel_revocation_middleware.js';
16
+ export { authkitClientGuard, AuthkitClientGuard } from './src/host/authkit_client_guard.js';
17
+ export type { UnauthorizedAccessConstructor } from './src/host/authkit_client_guard.js';
18
+ export { getAuthkit } from './src/host/request_authenticator.js';
19
+ export { default as AuthkitContextMiddleware } from './src/middleware/authkit_context_middleware.js';
20
+ export { lucidMirror } from './src/lucid_mirror.js';
21
+ export type { LucidMirrorOptions } from './src/lucid_mirror.js';
22
+ export { registerOidcClient } from './src/register_oidc_client.js';
23
+ export type { RegisterOidcClientOptions, PostLoginRedirects, } from './src/register_oidc_client.js';
24
+ export { validateLogoutToken, InvalidLogoutTokenError, InMemorySessionIndex, BACKCHANNEL_LOGOUT_EVENT, } from './src/backchannel_logout.js';
25
+ export type { ValidateLogoutTokenOptions, ValidatedLogoutToken, SessionIndex, SessionIndexEntry, } from './src/backchannel_logout.js';
26
+ export { generateDpopKeyPair, createDpopProof, dpopJwkThumbprint, } from './src/dpop.js';
27
+ export type { DpopKeyPair, CreateDpopProofInput } from './src/dpop.js';
28
+ export { verifyJwtAccessToken, clearJwksCache, } from './src/verify_access_token.js';
29
+ export type { VerifyJwtAccessTokenOptions, JwtAccessTokenClaims, } from './src/verify_access_token.js';
30
+ export type { TokenSet } from './src/types.js';
31
+ export type { Identity } from '@adonis-agora/authkit-core';
32
+ export { configure } from './commands/configure.js';
package/build/index.js CHANGED
@@ -1,15 +1,18 @@
1
- export { defineConfig, resolvers } from "./src/define_config.js";
2
- export { Authenticator } from "./src/authenticator.js";
3
- export { identityToUser, createUserinfoResolver, } from "./src/user_resolvers.js";
4
- export { buildAuthorizeUrl, buildEndSessionUrl, exchangeCode, exchangeToken, refreshTokens, generatePkce, } from "./src/oidc_login.js";
5
- export { discoverEndpoints, conventionEndpoints } from "./src/discovery.js";
6
- export { resilientFetch } from "./src/http/resilient_fetch.js";
7
- export { AuthkitClientManager } from "./providers/authkit_client_provider.js";
8
- export { lucidRevocationStore, DEFAULT_REVOCATION_TABLE, } from "./src/revocation/revocation_store.js";
9
- export { default as BackchannelRevocationMiddleware } from "./src/middleware/backchannel_revocation_middleware.js";
10
- export { lucidMirror } from "./src/lucid_mirror.js";
11
- export { registerOidcClient } from "./src/register_oidc_client.js";
12
- export { validateLogoutToken, InvalidLogoutTokenError, InMemorySessionIndex, BACKCHANNEL_LOGOUT_EVENT, } from "./src/backchannel_logout.js";
13
- export { generateDpopKeyPair, createDpopProof, dpopJwkThumbprint, } from "./src/dpop.js";
14
- export { verifyJwtAccessToken, clearJwksCache, } from "./src/verify_access_token.js";
15
- export { configure } from "./commands/configure.js";
1
+ export { defineConfig, resolvers } from './src/define_config.js';
2
+ export { Authenticator } from './src/authenticator.js';
3
+ export { identityToUser, createUserinfoResolver, } from './src/user_resolvers.js';
4
+ export { buildAuthorizeUrl, buildEndSessionUrl, exchangeCode, exchangeToken, refreshTokens, generatePkce, } from './src/oidc_login.js';
5
+ export { discoverEndpoints, conventionEndpoints } from './src/discovery.js';
6
+ export { resilientFetch } from './src/http/resilient_fetch.js';
7
+ export { AuthkitClientManager } from './providers/authkit_client_provider.js';
8
+ export { lucidRevocationStore, DEFAULT_REVOCATION_TABLE, } from './src/revocation/revocation_store.js';
9
+ export { default as BackchannelRevocationMiddleware } from './src/middleware/backchannel_revocation_middleware.js';
10
+ export { authkitClientGuard, AuthkitClientGuard } from './src/host/authkit_client_guard.js';
11
+ export { getAuthkit } from './src/host/request_authenticator.js';
12
+ export { default as AuthkitContextMiddleware } from './src/middleware/authkit_context_middleware.js';
13
+ export { lucidMirror } from './src/lucid_mirror.js';
14
+ export { registerOidcClient } from './src/register_oidc_client.js';
15
+ export { validateLogoutToken, InvalidLogoutTokenError, InMemorySessionIndex, BACKCHANNEL_LOGOUT_EVENT, } from './src/backchannel_logout.js';
16
+ export { generateDpopKeyPair, createDpopProof, dpopJwkThumbprint, } from './src/dpop.js';
17
+ export { verifyJwtAccessToken, clearJwksCache, } from './src/verify_access_token.js';
18
+ export { configure } from './commands/configure.js';
@@ -1,7 +1,7 @@
1
- import type { HttpContext } from "@adonisjs/core/http";
2
- import type { ApplicationService } from "@adonisjs/core/types";
3
- import { Authenticator } from "../src/authenticator.js";
4
- import type { ResolvedClientConfig } from "../src/define_config.js";
1
+ import type { HttpContext } from '@adonisjs/core/http';
2
+ import type { ApplicationService } from '@adonisjs/core/types';
3
+ import { Authenticator } from '../src/authenticator.js';
4
+ import type { ResolvedClientConfig } from '../src/define_config.js';
5
5
  /** Deps injetáveis (relógio/fetch) para testar `maybeRefresh` sem rede/tempo real. */
6
6
  export interface RefreshDeps {
7
7
  now?: () => number;
@@ -49,9 +49,9 @@ export declare class AuthkitClientManager {
49
49
  isImpersonating(ctx: HttpContext): boolean;
50
50
  createAuthenticator(ctx: HttpContext): Promise<Authenticator>;
51
51
  }
52
- declare module "@adonisjs/core/types" {
52
+ declare module '@adonisjs/core/types' {
53
53
  interface ContainerBindings {
54
- "authkit.client": AuthkitClientManager;
54
+ 'authkit.client': AuthkitClientManager;
55
55
  }
56
56
  }
57
57
  export default class AuthkitClientProvider {
@@ -1,8 +1,8 @@
1
- import { configProvider } from "@adonisjs/core";
2
- import { RuntimeException } from "@adonisjs/core/exceptions";
3
- import { Authenticator } from "../src/authenticator.js";
4
- import { validateLogoutToken } from "../src/backchannel_logout.js";
5
- import { exchangeToken, refreshTokens } from "../src/oidc_login.js";
1
+ import { configProvider } from '@adonisjs/core';
2
+ import { RuntimeException } from '@adonisjs/core/exceptions';
3
+ import { Authenticator } from '../src/authenticator.js';
4
+ import { validateLogoutToken } from '../src/backchannel_logout.js';
5
+ import { exchangeToken, refreshTokens } from '../src/oidc_login.js';
6
6
  /** Margem (ms) antes do `expiresAt` em que o access token é renovado proativamente. */
7
7
  const REFRESH_SKEW_MS = 60_000;
8
8
  export class AuthkitClientManager {
@@ -84,10 +84,10 @@ export class AuthkitClientManager {
84
84
  */
85
85
  async handleBackchannelLogout(ctx) {
86
86
  const { request, response } = ctx;
87
- response.header("Cache-Control", "no-store");
88
- const token = request.input("logout_token");
89
- if (!token || typeof token !== "string") {
90
- return response.badRequest({ error: "invalid_request" });
87
+ response.header('Cache-Control', 'no-store');
88
+ const token = request.input('logout_token');
89
+ if (!token || typeof token !== 'string') {
90
+ return response.badRequest({ error: 'invalid_request' });
91
91
  }
92
92
  let validated;
93
93
  try {
@@ -97,7 +97,7 @@ export class AuthkitClientManager {
97
97
  });
98
98
  }
99
99
  catch {
100
- return response.badRequest({ error: "invalid_request" });
100
+ return response.badRequest({ error: 'invalid_request' });
101
101
  }
102
102
  // Atualiza o índice local (se configurado) — revoga as sessões mapeadas.
103
103
  const index = this.config.sessionIndex;
@@ -126,9 +126,11 @@ export class AuthkitClientManager {
126
126
  */
127
127
  async impersonate(ctx, requestedSubject) {
128
128
  const session = ctx.session;
129
- const current = session?.get(this.config.sessionKey);
129
+ if (!session)
130
+ throw new Error('Sem sessão ativa para impersonar a partir dela');
131
+ const current = session.get(this.config.sessionKey);
130
132
  if (!current?.accessToken) {
131
- throw new Error("Sem sessão ativa para impersonar a partir dela");
133
+ throw new Error('Sem sessão ativa para impersonar a partir dela');
132
134
  }
133
135
  const impersonated = await exchangeToken({
134
136
  issuer: this.config.issuer,
@@ -144,7 +146,9 @@ export class AuthkitClientManager {
144
146
  /** Encerra a impersonação restaurando o token set original. Retorna false se não havia impersonação. */
145
147
  async stopImpersonating(ctx) {
146
148
  const session = ctx.session;
147
- const original = session?.get(this.#impersonatorKey);
149
+ if (!session)
150
+ return false;
151
+ const original = session.get(this.#impersonatorKey);
148
152
  if (!original)
149
153
  return false;
150
154
  session.put(this.config.sessionKey, original);
@@ -174,8 +178,8 @@ export default class AuthkitClientProvider {
174
178
  this.app = app;
175
179
  }
176
180
  register() {
177
- this.app.container.singleton("authkit.client", async () => {
178
- const value = this.app.config.get("authkit_client");
181
+ this.app.container.singleton('authkit.client', async () => {
182
+ const value = this.app.config.get('authkit_client');
179
183
  const config = (await configProvider.resolve(this.app, value));
180
184
  if (!config) {
181
185
  throw new RuntimeException('Config inválido em "config/authkit_client.ts". Use defineConfig de @adonis-agora/authkit-client.');
@@ -1,4 +1,4 @@
1
- import type { AuthkitClientManager } from "../providers/authkit_client_provider.js";
1
+ import type { AuthkitClientManager } from '../providers/authkit_client_provider.js';
2
2
  /**
3
3
  * Singleton accessor for the {@link AuthkitClientManager}, following the Adonis `services/main`
4
4
  * convention (like `db`, `mail`, `drive`). Lets an app write
@@ -1,4 +1,4 @@
1
- import app from "@adonisjs/core/services/app";
1
+ import app from '@adonisjs/core/services/app';
2
2
  /**
3
3
  * Singleton accessor for the {@link AuthkitClientManager}, following the Adonis `services/main`
4
4
  * convention (like `db`, `mail`, `drive`). Lets an app write
@@ -8,6 +8,6 @@ import app from "@adonisjs/core/services/app";
8
8
  */
9
9
  let manager;
10
10
  await app.booted(async () => {
11
- manager = await app.container.make("authkit.client");
11
+ manager = await app.container.make('authkit.client');
12
12
  });
13
13
  export { manager as default };
@@ -1,6 +1,6 @@
1
- import type { Identity, SessionResolver } from "@adonis-agora/authkit-core";
2
- import { type MetricsRecorder } from "@adonis-agora/authkit-core";
3
- import type { HttpContext } from "@adonisjs/core/http";
1
+ import type { Identity, SessionResolver } from '@adonis-agora/authkit-core';
2
+ import { type MetricsRecorder } from '@adonis-agora/authkit-core';
3
+ import type { HttpContext } from '@adonisjs/core/http';
4
4
  /** Contexto opcional repassado a `resolveUser` (extensão backward-compatible). */
5
5
  export interface ResolveUserContext {
6
6
  accessToken?: string;
@@ -1,5 +1,5 @@
1
- import { AUTHKIT_METRICS, NoopRecorder, } from "@adonis-agora/authkit-core";
2
- import { populateContext } from "./observability/context_bridge.js";
1
+ import { AUTHKIT_METRICS, NoopRecorder } from '@adonis-agora/authkit-core';
2
+ import { populateContext } from './observability/context_bridge.js';
3
3
  /**
4
4
  * Autenticador do request. O parâmetro `TUser` é o tipo do usuário do app que `resolveUser`
5
5
  * devolve — o app o fixa via augmentation de `HttpContext.auth` (`auth: Authenticator<AppUser>`),
@@ -47,7 +47,7 @@ export class Authenticator {
47
47
  async authenticate() {
48
48
  const identity = await this.getIdentity();
49
49
  if (!identity)
50
- throw new Error("Not authenticated");
50
+ throw new Error('Not authenticated');
51
51
  return identity;
52
52
  }
53
53
  async check() {
@@ -84,7 +84,7 @@ export class Authenticator {
84
84
  async getUserOrFail() {
85
85
  const user = await this.getUser();
86
86
  if (user === null) {
87
- throw new Error("Not authenticated: no user for the current session");
87
+ throw new Error('Not authenticated: no user for the current session');
88
88
  }
89
89
  return user;
90
90
  }
@@ -1,8 +1,8 @@
1
- import type { Identity } from "@adonis-agora/authkit-core";
2
- import type { SessionIndex } from "./backchannel_logout.js";
3
- import type { ResiliencePolicy } from "./http/resilient_fetch.js";
4
- import { type ResolverFactory, resolvers } from "./resolvers/factory.js";
5
- import type { RevocationStore } from "./revocation/revocation_store.js";
1
+ import type { Identity } from '@adonis-agora/authkit-core';
2
+ import type { SessionIndex } from './backchannel_logout.js';
3
+ import type { ResiliencePolicy } from './http/resilient_fetch.js';
4
+ import { type ResolverFactory, resolvers } from './resolvers/factory.js';
5
+ import type { RevocationStore } from './revocation/revocation_store.js';
6
6
  export { resolvers };
7
7
  /** Callback invocado quando um logout_token válido chega via Back-Channel Logout. */
8
8
  export type BackchannelLogoutCallback = (event: {
@@ -1,5 +1,5 @@
1
- import { configProvider } from "@adonisjs/core";
2
- import { resolvers } from "./resolvers/factory.js";
1
+ import { configProvider } from '@adonisjs/core';
2
+ import { resolvers } from './resolvers/factory.js';
3
3
  export { resolvers };
4
4
  export function defineConfig(config) {
5
5
  return configProvider.create(async () => {
@@ -22,9 +22,9 @@ export function defineConfig(config) {
22
22
  redirectUri: config.redirectUri,
23
23
  resolverFactory: config.resolver,
24
24
  resolveUser: config.resolveUser,
25
- sessionKey: config.sessionKey ?? "authkit",
26
- scopes: config.scopes ?? ["openid", "profile", "email", "offline_access", "roles"],
27
- globalRolesClaim: config.globalRolesClaim ?? "roles",
25
+ sessionKey: config.sessionKey ?? 'authkit',
26
+ scopes: config.scopes ?? ['openid', 'profile', 'email', 'offline_access', 'roles'],
27
+ globalRolesClaim: config.globalRolesClaim ?? 'roles',
28
28
  onBackchannelLogout,
29
29
  sessionIndex: config.sessionIndex,
30
30
  backchannelStore: store,
@@ -16,7 +16,7 @@
16
16
  * const tokens = await exchangeCode({ issuer, tokenEndpoint: endpoints.tokenEndpoint, ... })
17
17
  * ```
18
18
  */
19
- import { type ResiliencePolicy } from "./http/resilient_fetch.js";
19
+ import { type ResiliencePolicy } from './http/resilient_fetch.js';
20
20
  export interface OidcEndpoints {
21
21
  authorizationEndpoint: string;
22
22
  tokenEndpoint: string;
@@ -16,13 +16,13 @@
16
16
  * const tokens = await exchangeCode({ issuer, tokenEndpoint: endpoints.tokenEndpoint, ... })
17
17
  * ```
18
18
  */
19
- import { resilientFetch, } from "./http/resilient_fetch.js";
19
+ import { resilientFetch } from './http/resilient_fetch.js';
20
20
  /**
21
21
  * Convenção do oidc-provider (authkit-server) — usada como fallback quando o
22
22
  * documento de discovery não está acessível.
23
23
  */
24
24
  export function conventionEndpoints(issuer) {
25
- const base = issuer.replace(/\/$/, "");
25
+ const base = issuer.replace(/\/$/, '');
26
26
  return {
27
27
  authorizationEndpoint: `${base}/auth`,
28
28
  tokenEndpoint: `${base}/token`,
@@ -43,7 +43,7 @@ const cache = new Map();
43
43
  * - `overrides` sempre vencem, campo a campo.
44
44
  */
45
45
  export async function discoverEndpoints(issuer, options = {}) {
46
- const base = issuer.replace(/\/$/, "");
46
+ const base = issuer.replace(/\/$/, '');
47
47
  const ttl = options.cacheTtlMs ?? DEFAULT_TTL_MS;
48
48
  const cached = cache.get(base);
49
49
  let endpoints;
@@ -66,11 +66,11 @@ export async function discoverEndpoints(issuer, options = {}) {
66
66
  async function fetchDiscovery(base, fetchImpl, resilience) {
67
67
  const fallback = conventionEndpoints(base);
68
68
  try {
69
- const res = await resilientFetch(`${base}/.well-known/openid-configuration`, { headers: { accept: "application/json" } }, resilience, fetchImpl);
69
+ const res = await resilientFetch(`${base}/.well-known/openid-configuration`, { headers: { accept: 'application/json' } }, resilience, fetchImpl);
70
70
  if (!res.ok)
71
71
  return fallback;
72
72
  const doc = (await res.json());
73
- const str = (v) => typeof v === "string" && v ? v : undefined;
73
+ const str = (v) => (typeof v === 'string' && v ? v : undefined);
74
74
  return {
75
75
  authorizationEndpoint: str(doc.authorization_endpoint) ?? fallback.authorizationEndpoint,
76
76
  tokenEndpoint: str(doc.token_endpoint) ?? fallback.tokenEndpoint,
package/build/src/dpop.js CHANGED
@@ -1,5 +1,5 @@
1
- import { SignJWT, exportJWK, generateKeyPair, calculateJwkThumbprint, base64url, } from 'jose';
2
1
  import { createHash, randomUUID } from 'node:crypto';
2
+ import { SignJWT, base64url, calculateJwkThumbprint, exportJWK, generateKeyPair, } from 'jose';
3
3
  /**
4
4
  * Gera um par de chaves DPoP ES256 (P-256), exportável como JWK. ES256 é o alg
5
5
  * recomendado pelo RFC 9449 e o que o oidc-provider do authkit-server aceita.
@@ -0,0 +1,100 @@
1
+ import type { symbols } from '@adonisjs/auth';
2
+ import type { AuthClientResponse, GuardConfigProvider } 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 { Authenticator } from '../authenticator.js';
7
+ /**
8
+ * Construtor do `E_UNAUTHORIZED_ACCESS` do `@adonisjs/auth`, capturado no boot
9
+ * (ver {@link authkitClientGuard}) para que `getUserOrFail()` — que o contrato
10
+ * exige SÍNCRONO — possa lançá-lo sem um `await import()`.
11
+ */
12
+ export type UnauthorizedAccessConstructor = new (message: string, options: {
13
+ guardDriverName: string;
14
+ redirectTo?: string;
15
+ }) => Error;
16
+ /** O tipo de usuário REAL por trás de um user provider do `@adonisjs/auth`. */
17
+ type RealUser<UserProvider> = UserProvider extends SessionUserProviderContract<infer User> ? User : never;
18
+ /**
19
+ * Guard de `@adonisjs/auth` apoiado na sessão OIDC do `@adonis-agora/authkit-client`.
20
+ *
21
+ * A fonte da verdade de "quem está logado" é o `SessionResolver` do authkit
22
+ * (JWT/opaco/PAT, conforme `config/authkit_client.ts`) — o guard NÃO reimplementa
23
+ * resolução de sessão nem lê a sessão por conta própria. Ele só faz a ponte:
24
+ * pega o `sub` da `Identity` (`identity.userId`) e pede o usuário do app ao
25
+ * `provider`, exatamente como o `sessionGuard` nativo faria com o id que ele
26
+ * guarda na sessão.
27
+ *
28
+ * Consequência: `middleware.auth()`, `ctx.auth.user`, `auth.check()` e o Bouncer
29
+ * passam a funcionar sobre uma sessão do authkit.
30
+ */
31
+ export declare class AuthkitClientGuard<UserProvider extends SessionUserProviderContract<unknown>> {
32
+ #private;
33
+ /** Eventos do guard. Não emitimos nenhum (ainda) — o slot existe pro contrato. */
34
+ [symbols.GUARD_KNOWN_EVENTS]: unknown;
35
+ readonly driverName = "authkit_client";
36
+ authenticationAttempted: boolean;
37
+ isAuthenticated: boolean;
38
+ user?: RealUser<UserProvider>;
39
+ constructor(_name: string, ctx: HttpContext, userProvider: UserProvider, unauthorized: UnauthorizedAccessConstructor);
40
+ /**
41
+ * O {@link Authenticator} do authkit desta request (a `Identity` crua,
42
+ * `hasGlobalRole`, `toSharedProps`). Só existe depois de uma tentativa de
43
+ * autenticação; use `ctx.authkit` (via `authkit_context_middleware`) quando
44
+ * precisar dele em rota pública.
45
+ */
46
+ get authkit(): Authenticator | undefined;
47
+ /**
48
+ * Autentica a request: resolve a sessão do authkit e mapeia o `sub` para o
49
+ * usuário do app via `provider.findById`. Lança `E_UNAUTHORIZED_ACCESS` (o
50
+ * MESMO erro dos guards nativos, então o `middleware.auth()` e o handler de
51
+ * exceção do host tratam igual) quando não há sessão OU quando a sessão é
52
+ * válida mas não existe usuário local correspondente (fail-closed).
53
+ */
54
+ authenticate(): Promise<RealUser<UserProvider>>;
55
+ /** Igual aos guards nativos: `authenticate()` sem lançar. */
56
+ check(): Promise<boolean>;
57
+ /** O usuário autenticado, ou `E_UNAUTHORIZED_ACCESS`. */
58
+ getUserOrFail(): RealUser<UserProvider>;
59
+ /**
60
+ * Não implementado de propósito: a sessão deste guard é um TokenSet OIDC, e
61
+ * forjar um `id_token` assinado pelo IdP não é algo que a lib possa fazer a
62
+ * partir de um objeto de usuário. Use `@adonis-agora/authkit-testing`
63
+ * (`mintTestIdToken`) e plante o TokenSet na sessão, ou injete um
64
+ * `fakeAuthenticator()` em `ctx.authkit`.
65
+ */
66
+ authenticateAsClient(): Promise<AuthClientResponse>;
67
+ }
68
+ /**
69
+ * Configura o {@link AuthkitClientGuard} em `config/auth.ts`:
70
+ *
71
+ * ```ts
72
+ * import { defineConfig } from '@adonisjs/auth'
73
+ * import { sessionUserProvider } from '@adonisjs/auth/session'
74
+ * import { authkitClientGuard } from '@adonis-agora/authkit-client'
75
+ *
76
+ * export default defineConfig({
77
+ * default: 'web',
78
+ * guards: {
79
+ * web: authkitClientGuard({
80
+ * provider: sessionUserProvider({ model: () => import('#models/user') }),
81
+ * }),
82
+ * },
83
+ * })
84
+ * ```
85
+ *
86
+ * O `provider` é o MESMO contrato do `sessionGuard` nativo, então qualquer user
87
+ * provider de session serve. O `findById` recebe `identity.userId` (a claim
88
+ * `sub`) — a coluna de id do model precisa ser essa (é o que o `lucidMirror()`
89
+ * grava).
90
+ *
91
+ * Precedência com `resolveUser` de `config/authkit_client.ts`: são superfícies
92
+ * SEPARADAS e ambas continuam valendo. `ctx.auth.user` (framework) vem SEMPRE do
93
+ * `provider`; `ctx.authkit.getUser()` (authkit) continua vindo do `resolveUser`.
94
+ * O guard nunca chama `resolveUser`. Com o guard configurado, o normal é apagar
95
+ * o `resolveUser` e deixar só o `provider`.
96
+ */
97
+ export declare function authkitClientGuard<UserProvider extends SessionUserProviderContract<unknown>>(config: {
98
+ provider: UserProvider | ConfigProvider<UserProvider>;
99
+ }): GuardConfigProvider<(ctx: HttpContext) => AuthkitClientGuard<UserProvider>>;
100
+ export {};
@@ -0,0 +1,153 @@
1
+ import { RuntimeException } from '@adonisjs/core/exceptions';
2
+ import { getAuthkit } from './request_authenticator.js';
3
+ /**
4
+ * Guard de `@adonisjs/auth` apoiado na sessão OIDC do `@adonis-agora/authkit-client`.
5
+ *
6
+ * A fonte da verdade de "quem está logado" é o `SessionResolver` do authkit
7
+ * (JWT/opaco/PAT, conforme `config/authkit_client.ts`) — o guard NÃO reimplementa
8
+ * resolução de sessão nem lê a sessão por conta própria. Ele só faz a ponte:
9
+ * pega o `sub` da `Identity` (`identity.userId`) e pede o usuário do app ao
10
+ * `provider`, exatamente como o `sessionGuard` nativo faria com o id que ele
11
+ * guarda na sessão.
12
+ *
13
+ * Consequência: `middleware.auth()`, `ctx.auth.user`, `auth.check()` e o Bouncer
14
+ * passam a funcionar sobre uma sessão do authkit.
15
+ */
16
+ export class AuthkitClientGuard {
17
+ #ctx;
18
+ #userProvider;
19
+ #unauthorized;
20
+ #authkit;
21
+ driverName = 'authkit_client';
22
+ authenticationAttempted = false;
23
+ isAuthenticated = false;
24
+ user;
25
+ constructor(_name, ctx, userProvider, unauthorized) {
26
+ this.#ctx = ctx;
27
+ this.#userProvider = userProvider;
28
+ this.#unauthorized = unauthorized;
29
+ }
30
+ /**
31
+ * O {@link Authenticator} do authkit desta request (a `Identity` crua,
32
+ * `hasGlobalRole`, `toSharedProps`). Só existe depois de uma tentativa de
33
+ * autenticação; use `ctx.authkit` (via `authkit_context_middleware`) quando
34
+ * precisar dele em rota pública.
35
+ */
36
+ get authkit() {
37
+ return this.#authkit;
38
+ }
39
+ #failed() {
40
+ this.isAuthenticated = false;
41
+ this.user = undefined;
42
+ return new this.#unauthorized('Invalid or expired user session', {
43
+ guardDriverName: this.driverName,
44
+ });
45
+ }
46
+ /**
47
+ * Autentica a request: resolve a sessão do authkit e mapeia o `sub` para o
48
+ * usuário do app via `provider.findById`. Lança `E_UNAUTHORIZED_ACCESS` (o
49
+ * MESMO erro dos guards nativos, então o `middleware.auth()` e o handler de
50
+ * exceção do host tratam igual) quando não há sessão OU quando a sessão é
51
+ * válida mas não existe usuário local correspondente (fail-closed).
52
+ */
53
+ async authenticate() {
54
+ if (this.authenticationAttempted)
55
+ return this.getUserOrFail();
56
+ this.authenticationAttempted = true;
57
+ const authkit = await getAuthkit(this.#ctx);
58
+ this.#authkit = authkit;
59
+ const identity = await authkit.getIdentity();
60
+ if (!identity)
61
+ throw this.#failed();
62
+ // `identity.userId` É a claim `sub` — o único id estável por conta que a
63
+ // `Identity` tem. (Ela NÃO tem `id`.)
64
+ const providerUser = await this.#userProvider.findById(identity.userId);
65
+ if (!providerUser)
66
+ throw this.#failed();
67
+ this.user = providerUser.getOriginal();
68
+ this.isAuthenticated = true;
69
+ return this.user;
70
+ }
71
+ /** Igual aos guards nativos: `authenticate()` sem lançar. */
72
+ async check() {
73
+ try {
74
+ await this.authenticate();
75
+ return true;
76
+ }
77
+ catch (error) {
78
+ if (error instanceof this.#unauthorized)
79
+ return false;
80
+ throw error;
81
+ }
82
+ }
83
+ /** O usuário autenticado, ou `E_UNAUTHORIZED_ACCESS`. */
84
+ getUserOrFail() {
85
+ if (!this.user) {
86
+ throw new this.#unauthorized('Invalid or expired user session', {
87
+ guardDriverName: this.driverName,
88
+ });
89
+ }
90
+ return this.user;
91
+ }
92
+ /**
93
+ * Não implementado de propósito: a sessão deste guard é um TokenSet OIDC, e
94
+ * forjar um `id_token` assinado pelo IdP não é algo que a lib possa fazer a
95
+ * partir de um objeto de usuário. Use `@adonis-agora/authkit-testing`
96
+ * (`mintTestIdToken`) e plante o TokenSet na sessão, ou injete um
97
+ * `fakeAuthenticator()` em `ctx.authkit`.
98
+ */
99
+ async authenticateAsClient() {
100
+ throw new RuntimeException('authkitClientGuard não suporta "loginAs" nos plugins de teste: a sessão é um TokenSet OIDC ' +
101
+ 'assinado pelo IdP. Use mintTestIdToken() de @adonis-agora/authkit-testing e plante o ' +
102
+ 'TokenSet na sessão, ou injete fakeAuthenticator() em ctx.authkit.');
103
+ }
104
+ }
105
+ /**
106
+ * Configura o {@link AuthkitClientGuard} em `config/auth.ts`:
107
+ *
108
+ * ```ts
109
+ * import { defineConfig } from '@adonisjs/auth'
110
+ * import { sessionUserProvider } from '@adonisjs/auth/session'
111
+ * import { authkitClientGuard } from '@adonis-agora/authkit-client'
112
+ *
113
+ * export default defineConfig({
114
+ * default: 'web',
115
+ * guards: {
116
+ * web: authkitClientGuard({
117
+ * provider: sessionUserProvider({ model: () => import('#models/user') }),
118
+ * }),
119
+ * },
120
+ * })
121
+ * ```
122
+ *
123
+ * O `provider` é o MESMO contrato do `sessionGuard` nativo, então qualquer user
124
+ * provider de session serve. O `findById` recebe `identity.userId` (a claim
125
+ * `sub`) — a coluna de id do model precisa ser essa (é o que o `lucidMirror()`
126
+ * grava).
127
+ *
128
+ * Precedência com `resolveUser` de `config/authkit_client.ts`: são superfícies
129
+ * SEPARADAS e ambas continuam valendo. `ctx.auth.user` (framework) vem SEMPRE do
130
+ * `provider`; `ctx.authkit.getUser()` (authkit) continua vindo do `resolveUser`.
131
+ * O guard nunca chama `resolveUser`. Com o guard configurado, o normal é apagar
132
+ * o `resolveUser` e deixar só o `provider`.
133
+ */
134
+ export function authkitClientGuard(config) {
135
+ return {
136
+ async resolver(name, app) {
137
+ let unauthorized;
138
+ try {
139
+ const auth = (await import('@adonisjs/auth'));
140
+ unauthorized = auth.errors.E_UNAUTHORIZED_ACCESS;
141
+ }
142
+ catch (error) {
143
+ throw new RuntimeException('authkitClientGuard() precisa de "@adonisjs/auth" instalado (é um peer opcional do ' +
144
+ '@adonis-agora/authkit-client, só necessário se você plugar este guard em config/auth.ts). ' +
145
+ 'Rode `npm i @adonisjs/auth` (ou pnpm/yarn).', { cause: error });
146
+ }
147
+ const provider = 'resolver' in config.provider
148
+ ? (await config.provider.resolver(app))
149
+ : config.provider;
150
+ return (ctx) => new AuthkitClientGuard(name, ctx, provider, unauthorized);
151
+ },
152
+ };
153
+ }
@@ -0,0 +1,31 @@
1
+ import type { HttpContext } from '@adonisjs/core/http';
2
+ import type { Authenticator } from '../authenticator.js';
3
+ /**
4
+ * Resolve (uma vez por request) o {@link Authenticator} do authkit e o memoiza
5
+ * em `ctx.authkit`.
6
+ *
7
+ * É o ÚNICO caminho de resolução de sessão da lib: passa pelo
8
+ * `AuthkitClientManager`, então o refresh proativo do token (`maybeRefresh`), o
9
+ * `SessionResolver` configurado, as métricas e a ponte do `@agora/context`
10
+ * continuam valendo — tanto para quem usa `authkit_middleware` (legado) quanto
11
+ * para quem usa o `authkitClientGuard` de `@adonisjs/auth`.
12
+ *
13
+ * Memoiza em `ctx.authkit` de propósito: quando o host registra o
14
+ * `authkit_context_middleware` E o guard, os dois compartilham a MESMA
15
+ * instância, e a sessão é resolvida (e o token renovado) uma vez só.
16
+ */
17
+ export declare function getAuthkit(ctx: HttpContext): Promise<Authenticator>;
18
+ declare module '@adonisjs/core/http' {
19
+ interface HttpContext {
20
+ /**
21
+ * Superfície específica do authkit (`getIdentity`, `hasGlobalRole`, a
22
+ * `Identity` crua). Populada pelo `authkit_context_middleware` e, no
23
+ * caminho do guard, pelo próprio `authkitClientGuard` ao autenticar.
24
+ *
25
+ * Existe para que o `@adonisjs/auth` possa ser o dono de `ctx.auth` sem que
26
+ * o host perca o que só o authkit sabe. Quem NÃO usa o guard continua com o
27
+ * `Authenticator` do authkit direto em `ctx.auth` (ver `types.ts`).
28
+ */
29
+ authkit: Authenticator;
30
+ }
31
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Resolve (uma vez por request) o {@link Authenticator} do authkit e o memoiza
3
+ * em `ctx.authkit`.
4
+ *
5
+ * É o ÚNICO caminho de resolução de sessão da lib: passa pelo
6
+ * `AuthkitClientManager`, então o refresh proativo do token (`maybeRefresh`), o
7
+ * `SessionResolver` configurado, as métricas e a ponte do `@agora/context`
8
+ * continuam valendo — tanto para quem usa `authkit_middleware` (legado) quanto
9
+ * para quem usa o `authkitClientGuard` de `@adonisjs/auth`.
10
+ *
11
+ * Memoiza em `ctx.authkit` de propósito: quando o host registra o
12
+ * `authkit_context_middleware` E o guard, os dois compartilham a MESMA
13
+ * instância, e a sessão é resolvida (e o token renovado) uma vez só.
14
+ */
15
+ export async function getAuthkit(ctx) {
16
+ const existing = ctx.authkit;
17
+ if (existing)
18
+ return existing;
19
+ const manager = await ctx.containerResolver.make('authkit.client');
20
+ await manager.maybeRefresh(ctx);
21
+ const authenticator = await manager.createAuthenticator(ctx);
22
+ ctx.authkit = authenticator;
23
+ return authenticator;
24
+ }
@@ -0,0 +1,28 @@
1
+ import type { HttpContext } from '@adonisjs/core/http';
2
+ import type { NextFn } from '@adonisjs/core/types/http';
3
+ /**
4
+ * Popula `ctx.authkit` (o `Authenticator` do authkit) e renova o token
5
+ * proativamente — SEM tocar em `ctx.auth`.
6
+ *
7
+ * É o par do `authkitClientGuard`: quem adota o guard entrega `ctx.auth` ao
8
+ * `@adonisjs/auth` (via `@adonisjs/auth/initialize_auth_middleware`) e registra
9
+ * ESTE middleware para continuar alcançando a superfície específica do authkit
10
+ * (`hasGlobalRole`, a `Identity` crua, `toSharedProps`) em qualquer rota,
11
+ * inclusive nas públicas.
12
+ *
13
+ * ```ts
14
+ * // start/kernel.ts
15
+ * router.use([
16
+ * () => import('@adonisjs/auth/initialize_auth_middleware'),
17
+ * () => import('@adonis-agora/authkit-client/authkit_context_middleware'),
18
+ * ])
19
+ * ```
20
+ *
21
+ * Não substitui o `authkit_middleware`: quem NÃO usa `@adonisjs/auth` continua
22
+ * com o `authkit_middleware`, que segue colocando o `Authenticator` em
23
+ * `ctx.auth` como sempre. Registrar os dois não faz sentido (e resolveria a
24
+ * sessão duas vezes em objetos distintos).
25
+ */
26
+ export default class AuthkitContextMiddleware {
27
+ handle(ctx: HttpContext, next: NextFn): Promise<any>;
28
+ }
@@ -0,0 +1,30 @@
1
+ import { getAuthkit } from '../host/request_authenticator.js';
2
+ /**
3
+ * Popula `ctx.authkit` (o `Authenticator` do authkit) e renova o token
4
+ * proativamente — SEM tocar em `ctx.auth`.
5
+ *
6
+ * É o par do `authkitClientGuard`: quem adota o guard entrega `ctx.auth` ao
7
+ * `@adonisjs/auth` (via `@adonisjs/auth/initialize_auth_middleware`) e registra
8
+ * ESTE middleware para continuar alcançando a superfície específica do authkit
9
+ * (`hasGlobalRole`, a `Identity` crua, `toSharedProps`) em qualquer rota,
10
+ * inclusive nas públicas.
11
+ *
12
+ * ```ts
13
+ * // start/kernel.ts
14
+ * router.use([
15
+ * () => import('@adonisjs/auth/initialize_auth_middleware'),
16
+ * () => import('@adonis-agora/authkit-client/authkit_context_middleware'),
17
+ * ])
18
+ * ```
19
+ *
20
+ * Não substitui o `authkit_middleware`: quem NÃO usa `@adonisjs/auth` continua
21
+ * com o `authkit_middleware`, que segue colocando o `Authenticator` em
22
+ * `ctx.auth` como sempre. Registrar os dois não faz sentido (e resolveria a
23
+ * sessão duas vezes em objetos distintos).
24
+ */
25
+ export default class AuthkitContextMiddleware {
26
+ async handle(ctx, next) {
27
+ await getAuthkit(ctx);
28
+ return next();
29
+ }
30
+ }
@@ -26,7 +26,6 @@ export default class BackchannelRevocationMiddleware {
26
26
  const authTime = typeof claims?.iat === 'number' ? claims.iat : undefined;
27
27
  if ((sid || sub) && (await store.isRevoked({ sid, sub, authTime }))) {
28
28
  // session é augmentado por @adonisjs/session no app host; na lib usamos cast.
29
- ;
30
29
  ctx.session?.forget(sessionKey);
31
30
  }
32
31
  }
@@ -1,4 +1,4 @@
1
- import type { Identity } from "@adonis-agora/authkit-core";
1
+ import type { Identity } from '@adonis-agora/authkit-core';
2
2
  /**
3
3
  * Popula o contexto do Agora a partir de uma identidade recém-resolvida:
4
4
  * `userRef` ({ type: 'user', id: userId }), os `globalRoles` da identidade e,
@@ -4,17 +4,17 @@
4
4
  * nunca importa `@adonis-agora/context` — e degrada para no-op quando o context
5
5
  * não está instalado. Mesmo idioma da `diagnostics_bridge`.
6
6
  */
7
- const SET_SLOT = Symbol.for("@agora/context:set");
7
+ const SET_SLOT = Symbol.for('@agora/context:set');
8
8
  /**
9
9
  * Claims OIDC candidatas a "organização/tenant ativo". A primeira presente (e
10
10
  * string não vazia) vira o `tenantId`. Não falha se nenhuma existir.
11
11
  */
12
12
  const TENANT_CLAIMS = [
13
- "active_organization_id",
14
- "org_id",
15
- "organization_id",
16
- "tenant_id",
17
- "tid",
13
+ 'active_organization_id',
14
+ 'org_id',
15
+ 'organization_id',
16
+ 'tenant_id',
17
+ 'tid',
18
18
  ];
19
19
  /** Deriva o tenant ativo das claims cruas, ou `undefined` quando ausente. */
20
20
  function deriveTenant(raw) {
@@ -22,7 +22,7 @@ function deriveTenant(raw) {
22
22
  return undefined;
23
23
  for (const claim of TENANT_CLAIMS) {
24
24
  const value = raw[claim];
25
- if (typeof value === "string" && value.length > 0)
25
+ if (typeof value === 'string' && value.length > 0)
26
26
  return value;
27
27
  }
28
28
  return undefined;
@@ -40,7 +40,7 @@ export function populateContext(identity) {
40
40
  return;
41
41
  const tenant = deriveTenant(identity.raw);
42
42
  set({
43
- userRef: { type: "user", id: identity.userId },
43
+ userRef: { type: 'user', id: identity.userId },
44
44
  globalRoles: identity.globalRoles,
45
45
  ...(tenant ? { tenantId: tenant } : {}),
46
46
  });
@@ -1,5 +1,5 @@
1
- import type { TokenSet } from "./types.js";
2
- import { type ResiliencePolicy } from "./http/resilient_fetch.js";
1
+ import { type ResiliencePolicy } from './http/resilient_fetch.js';
2
+ import type { TokenSet } from './types.js';
3
3
  /**
4
4
  * Gera um par PKCE (code_verifier + code_challenge) usando o método S256.
5
5
  *
@@ -1,6 +1,6 @@
1
- import { createHash, randomBytes } from "node:crypto";
2
- import { base64url } from "jose";
3
- import { resilientFetch, } from "./http/resilient_fetch.js";
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { base64url } from 'jose';
3
+ import { resilientFetch } from './http/resilient_fetch.js';
4
4
  /**
5
5
  * Gera um par PKCE (code_verifier + code_challenge) usando o método S256.
6
6
  *
@@ -11,18 +11,18 @@ import { resilientFetch, } from "./http/resilient_fetch.js";
11
11
  */
12
12
  export async function generatePkce() {
13
13
  const verifier = base64url.encode(randomBytes(32));
14
- const challenge = base64url.encode(createHash("sha256").update(verifier).digest());
15
- return { verifier, challenge, method: "S256" };
14
+ const challenge = base64url.encode(createHash('sha256').update(verifier).digest());
15
+ return { verifier, challenge, method: 'S256' };
16
16
  }
17
17
  export function buildAuthorizeUrl(p) {
18
18
  const url = new URL(p.authorizationEndpoint ?? `${p.issuer}/auth`);
19
- url.searchParams.set("client_id", p.clientId);
20
- url.searchParams.set("response_type", "code");
21
- url.searchParams.set("redirect_uri", p.redirectUri);
22
- url.searchParams.set("scope", p.scopes.join(" "));
23
- url.searchParams.set("state", p.state);
24
- url.searchParams.set("code_challenge", p.codeChallenge);
25
- url.searchParams.set("code_challenge_method", "S256");
19
+ url.searchParams.set('client_id', p.clientId);
20
+ url.searchParams.set('response_type', 'code');
21
+ url.searchParams.set('redirect_uri', p.redirectUri);
22
+ url.searchParams.set('scope', p.scopes.join(' '));
23
+ url.searchParams.set('state', p.state);
24
+ url.searchParams.set('code_challenge', p.codeChallenge);
25
+ url.searchParams.set('code_challenge_method', 'S256');
26
26
  if (p.extraParams) {
27
27
  for (const [key, value] of Object.entries(p.extraParams)) {
28
28
  if (value !== undefined && value !== null)
@@ -41,14 +41,14 @@ export function buildAuthorizeUrl(p) {
41
41
  export function buildEndSessionUrl(p) {
42
42
  const url = new URL(p.endSessionEndpoint ?? `${p.issuer}/session/end`);
43
43
  if (p.idToken)
44
- url.searchParams.set("id_token_hint", p.idToken);
44
+ url.searchParams.set('id_token_hint', p.idToken);
45
45
  if (p.postLogoutRedirectUri) {
46
- url.searchParams.set("post_logout_redirect_uri", p.postLogoutRedirectUri);
46
+ url.searchParams.set('post_logout_redirect_uri', p.postLogoutRedirectUri);
47
47
  }
48
48
  if (p.clientId)
49
- url.searchParams.set("client_id", p.clientId);
49
+ url.searchParams.set('client_id', p.clientId);
50
50
  if (p.state)
51
- url.searchParams.set("state", p.state);
51
+ url.searchParams.set('state', p.state);
52
52
  return url.toString();
53
53
  }
54
54
  /**
@@ -58,32 +58,30 @@ export function buildEndSessionUrl(p) {
58
58
  */
59
59
  async function tokenEndpoint(issuer, body, fetchImpl = fetch, endpoint, resilience) {
60
60
  const res = await resilientFetch(endpoint ?? `${issuer}/token`, {
61
- method: "POST",
62
- headers: { "content-type": "application/x-www-form-urlencoded" },
61
+ method: 'POST',
62
+ headers: { 'content-type': 'application/x-www-form-urlencoded' },
63
63
  body: body.toString(),
64
64
  }, resilience, fetchImpl);
65
65
  if (!res.ok)
66
66
  throw new Error(`Token endpoint returned ${res.status}`);
67
67
  const json = (await res.json());
68
68
  return {
69
- idToken: json.id_token ?? "",
69
+ idToken: json.id_token ?? '',
70
70
  accessToken: json.access_token,
71
71
  refreshToken: json.refresh_token,
72
- expiresAt: json.expires_in
73
- ? Date.now() + json.expires_in * 1000
74
- : undefined,
72
+ expiresAt: json.expires_in ? Date.now() + json.expires_in * 1000 : undefined,
75
73
  };
76
74
  }
77
75
  export async function exchangeCode(p) {
78
76
  const body = new URLSearchParams({
79
- grant_type: "authorization_code",
77
+ grant_type: 'authorization_code',
80
78
  code: p.code,
81
79
  redirect_uri: p.redirectUri,
82
80
  client_id: p.clientId,
83
81
  code_verifier: p.codeVerifier,
84
82
  });
85
83
  if (p.clientSecret)
86
- body.set("client_secret", p.clientSecret);
84
+ body.set('client_secret', p.clientSecret);
87
85
  return tokenEndpoint(p.issuer, body, p.fetchImpl, p.tokenEndpoint, p.resilience);
88
86
  }
89
87
  /**
@@ -96,28 +94,28 @@ export async function exchangeCode(p) {
96
94
  */
97
95
  export async function refreshTokens(p) {
98
96
  const body = new URLSearchParams({
99
- grant_type: "refresh_token",
97
+ grant_type: 'refresh_token',
100
98
  refresh_token: p.refreshToken,
101
99
  client_id: p.clientId,
102
100
  });
103
101
  if (p.scope)
104
- body.set("scope", p.scope);
102
+ body.set('scope', p.scope);
105
103
  if (p.clientSecret)
106
- body.set("client_secret", p.clientSecret);
104
+ body.set('client_secret', p.clientSecret);
107
105
  return tokenEndpoint(p.issuer, body, p.fetchImpl, p.tokenEndpoint, p.resilience);
108
106
  }
109
107
  /** Troca o token do ator por um token do usuário-alvo (RFC 8693), p/ impersonation. */
110
108
  export async function exchangeToken(p) {
111
109
  const body = new URLSearchParams({
112
- grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
110
+ grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
113
111
  subject_token: p.subjectToken,
114
- subject_token_type: "urn:ietf:params:oauth:token-type:access_token",
112
+ subject_token_type: 'urn:ietf:params:oauth:token-type:access_token',
115
113
  requested_subject: p.requestedSubject,
116
114
  client_id: p.clientId,
117
115
  });
118
116
  if (p.scope)
119
- body.set("scope", p.scope);
117
+ body.set('scope', p.scope);
120
118
  if (p.clientSecret)
121
- body.set("client_secret", p.clientSecret);
119
+ body.set('client_secret', p.clientSecret);
122
120
  return tokenEndpoint(p.issuer, body, p.fetchImpl, p.tokenEndpoint, p.resilience);
123
121
  }
@@ -17,7 +17,7 @@ export interface RegisterOidcClientOptions {
17
17
  * Hook após troca de code bem-sucedida. Retorne uma STRING para redirecionar
18
18
  * para uma rota específica; retorne void para cair em `redirects`/`/`.
19
19
  */
20
- afterLogin?: (ctx: HttpContext, identity: Identity | null) => Promise<string | void> | string | void;
20
+ afterLogin?: (ctx: HttpContext, identity: Identity | null) => Promise<string | undefined> | string | undefined;
21
21
  /** Redirect pós-login por papel (usado quando `afterLogin` não retorna string). */
22
22
  redirects?: PostLoginRedirects;
23
23
  /** Destino pós-logout. Default: origem do redirectUri + '/'. */
@@ -52,7 +52,6 @@ export function registerOidcClient(router, options = {}) {
52
52
  const pkce = ctx.session?.get(pkceKey);
53
53
  // Callback velho/expirado (state perdido ou code ausente) → recomeça o login.
54
54
  if (!pkce || pkce.state !== state || !code) {
55
- ;
56
55
  ctx.session?.forget(pkceKey);
57
56
  return ctx.response.redirect(`${prefix}/login`);
58
57
  }
@@ -85,7 +84,7 @@ export function registerOidcClient(router, options = {}) {
85
84
  ctx.session?.forget(cfg.sessionKey);
86
85
  const postLogoutRedirectUri = typeof options.postLogoutRedirect === 'function'
87
86
  ? options.postLogoutRedirect(ctx)
88
- : (options.postLogoutRedirect ?? new URL(cfg.redirectUri).origin + '/');
87
+ : (options.postLogoutRedirect ?? `${new URL(cfg.redirectUri).origin}/`);
89
88
  const endSessionUrl = buildEndSessionUrl({
90
89
  issuer: cfg.issuer,
91
90
  idToken,
@@ -1,7 +1,7 @@
1
+ import { getTokenFromSource } from '../token_source.js';
1
2
  import { JwtResolver } from './jwt_resolver.js';
2
- import { PatResolver } from './pat_resolver.js';
3
3
  import { OpaqueResolver } from './opaque_resolver.js';
4
- import { getTokenFromSource } from '../token_source.js';
4
+ import { PatResolver } from './pat_resolver.js';
5
5
  export const resolvers = {
6
6
  jwt(config = {}) {
7
7
  const tokenSource = config.tokenSource ?? 'session';
@@ -1,5 +1,5 @@
1
- import type { HttpContext } from '@adonisjs/core/http';
2
1
  import type { Identity, SessionResolver } from '@adonis-agora/authkit-core';
2
+ import type { HttpContext } from '@adonisjs/core/http';
3
3
  export interface JwtResolverConfig {
4
4
  issuer: string;
5
5
  jwksUri: string;
@@ -1,6 +1,17 @@
1
1
  import { createRemoteJWKSet, jwtVerify } from 'jose';
2
2
  import { buildIdentityFromClaims } from './identity.js';
3
- const DEFAULT_ALGS = ['RS256', 'RS384', 'RS512', 'PS256', 'PS384', 'PS512', 'ES256', 'ES384', 'ES512', 'EdDSA'];
3
+ const DEFAULT_ALGS = [
4
+ 'RS256',
5
+ 'RS384',
6
+ 'RS512',
7
+ 'PS256',
8
+ 'PS384',
9
+ 'PS512',
10
+ 'ES256',
11
+ 'ES384',
12
+ 'ES512',
13
+ 'EdDSA',
14
+ ];
4
15
  export class JwtResolver {
5
16
  config;
6
17
  #jwks;
@@ -1,5 +1,5 @@
1
- import type { HttpContext } from '@adonisjs/core/http';
2
1
  import type { Identity, SessionResolver } from '@adonis-agora/authkit-core';
2
+ import type { HttpContext } from '@adonisjs/core/http';
3
3
  import { type TokenSource } from '../token_source.js';
4
4
  type FetchImpl = (url: string, init: any) => Promise<{
5
5
  ok: boolean;
@@ -1,5 +1,5 @@
1
- import type { HttpContext } from '@adonisjs/core/http';
2
1
  import type { Identity, SessionResolver } from '@adonis-agora/authkit-core';
2
+ import type { HttpContext } from '@adonisjs/core/http';
3
3
  type FetchImpl = (url: string, init: any) => Promise<{
4
4
  ok: boolean;
5
5
  json: () => Promise<any>;
package/build/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { Authenticator } from './src/authenticator.js';
2
1
  import type { AuthkitClientManager } from './providers/authkit_client_provider.js';
2
+ import type { Authenticator } from './src/authenticator.js';
3
3
  declare module '@adonisjs/core/http' {
4
4
  interface HttpContext {
5
5
  auth: Authenticator;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adonis-agora/authkit-client",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
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",
@@ -38,7 +38,9 @@
38
38
  "./types": "./build/index.js",
39
39
  "./services/main": "./build/services/main.js",
40
40
  "./authkit_client_provider": "./build/providers/authkit_client_provider.js",
41
+ "./auth": "./build/src/host/authkit_client_guard.js",
41
42
  "./authkit_middleware": "./build/src/middleware/authkit_middleware.js",
43
+ "./authkit_context_middleware": "./build/src/middleware/authkit_context_middleware.js",
42
44
  "./backchannel_revocation_middleware": "./build/src/middleware/backchannel_revocation_middleware.js",
43
45
  "./auth_middleware": "./build/src/middleware/auth_middleware.js",
44
46
  "./silent_auth_middleware": "./build/src/middleware/silent_auth_middleware.js",
@@ -54,12 +56,16 @@
54
56
  ]
55
57
  },
56
58
  "peerDependencies": {
59
+ "@adonisjs/auth": "^10.1.0",
57
60
  "@adonisjs/core": "^7.3.3",
58
61
  "@adonisjs/session": "^8.1.0",
59
62
  "@adonisjs/lucid": "^22.4.2",
60
63
  "@adonis-agora/resilience": "^0.1.0"
61
64
  },
62
65
  "peerDependenciesMeta": {
66
+ "@adonisjs/auth": {
67
+ "optional": true
68
+ },
63
69
  "@adonisjs/lucid": {
64
70
  "optional": true
65
71
  },
@@ -72,6 +78,7 @@
72
78
  "@adonis-agora/authkit-core": "0.7.0"
73
79
  },
74
80
  "devDependencies": {
81
+ "@adonisjs/auth": "10.1.0",
75
82
  "@adonisjs/core": "7.3.5",
76
83
  "@adonis-agora/resilience": "^0.1.0",
77
84
  "@adonisjs/session": "8.1.0",
@@ -83,11 +90,11 @@
83
90
  "ioredis-mock": "^8.9.0",
84
91
  "@types/node": "25.9.1",
85
92
  "typescript": "6.0.3",
86
- "@adonis-agora/authkit-server": "0.36.0"
93
+ "@adonis-agora/authkit-server": "0.55.0"
87
94
  },
88
95
  "scripts": {
89
96
  "build": "tsc && node -e \"require('node:fs').cpSync('stubs','build/stubs',{recursive:true,filter:(s)=>!s.endsWith('.ts')})\"",
90
- "typecheck": "tsc --noEmit",
97
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.tests.json",
91
98
  "test": "node --import=@poppinss/ts-exec bin/test.ts"
92
99
  }
93
100
  }
package/stubs/main.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { fileURLToPath } from 'node:url'
2
- export const stubsRoot = fileURLToPath(new URL('./', import.meta.url))
1
+ import { fileURLToPath } from 'node:url';
2
+ export const stubsRoot = fileURLToPath(new URL('./', import.meta.url));