@adonis-agora/authkit-client 1.0.0-rc.3 → 1.0.0-rc.4
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 +6 -0
- package/build/index.d.ts +1 -0
- package/build/providers/authkit_client_provider.d.ts +7 -0
- package/build/providers/authkit_client_provider.js +14 -0
- package/build/src/authenticator.d.ts +3 -0
- package/build/src/authenticator.js +1 -1
- package/build/src/define_config.d.ts +9 -0
- package/build/src/define_config.js +1 -0
- package/build/src/middleware/authkit_context_middleware.d.ts +5 -0
- package/build/src/middleware/authkit_context_middleware.js +18 -1
- package/build/src/observability/context_bridge.d.ts +59 -3
- package/build/src/observability/context_bridge.js +99 -11
- package/build/src/resolvers/factory.d.ts +2 -0
- package/build/src/resolvers/factory.js +3 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @adonis-agora/authkit-client
|
|
2
2
|
|
|
3
|
+
## 1.0.0-rc.4
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#465](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/465) [`6bd9575`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/6bd95759752c0fac14a061875abebb38c0e238b7) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - The active organization is published as the request's tenant in `@adonis-agora/context`: `Context.tenant()` returns `{ id, kind: 'organization', source: 'authkit', slug, role }` (and `tenantId` as before). New client option `tenancy`: `eager` resolves the session at request start (default on when `config/context.ts` enables tenancy and the resolver is `jwt`), `personal` makes the user the tenant when there is no active organization (off by default), and `resolve` maps the session to your own (optionally nested) tenants. A tenant the app sets itself with `Context.setTenant` is never overwritten. authkit-server ships `authkitTenantLabels()`, a label resolver for `config/context.ts` → `tenancy.label` that shows organization names in the consoles, and account stores gain the optional `findOrgsByIds` (implemented by the Lucid store).
|
|
8
|
+
|
|
3
9
|
## 1.0.0-rc.3
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/build/index.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export { default as AuthkitContextMiddleware } from './src/middleware/authkit_co
|
|
|
30
30
|
export { default as BackchannelRevocationMiddleware } from './src/middleware/backchannel_revocation_middleware.js';
|
|
31
31
|
export type { RequireOrgMiddlewareOptions } from './src/middleware/require_org_middleware.js';
|
|
32
32
|
export { default as RequireOrgMiddleware } from './src/middleware/require_org_middleware.js';
|
|
33
|
+
export type { ClientTenancyOptions, ContextTenant, ContextTenantLink, TenantInput, } from './src/observability/context_bridge.js';
|
|
33
34
|
export type { AuthorizeParams, EndSessionParams, ExchangeParams, RefreshParams, } from './src/oidc_login.js';
|
|
34
35
|
export { buildAuthorizeUrl, buildEndSessionUrl, exchangeCode, exchangeToken, generatePkce, refreshTokens, } from './src/oidc_login.js';
|
|
35
36
|
export type { PostLoginRedirects, RegisterOidcClientOptions, } from './src/register_oidc_client.js';
|
|
@@ -162,6 +162,13 @@ export declare class AuthkitClientManager {
|
|
|
162
162
|
* sobrevive à sessão que a parqueou.
|
|
163
163
|
*/
|
|
164
164
|
startSession(ctx: HttpContext, tokenSet: TokenSet): void;
|
|
165
|
+
/**
|
|
166
|
+
* Whether `authkit_context_middleware` resolves the session at request start, so
|
|
167
|
+
* the tenant is in `@adonis-agora/context` before any handler runs. `tenancy.eager`
|
|
168
|
+
* when set; otherwise `true` only when context tenancy is enabled and the resolver
|
|
169
|
+
* is `resolvers.jwt()` (local verify, no network).
|
|
170
|
+
*/
|
|
171
|
+
eagerTenancy(): boolean;
|
|
165
172
|
/**
|
|
166
173
|
* The request's {@link Authenticator}. `options.resolver` replaces the configured
|
|
167
174
|
* resolver for this authenticator only (e.g. a guard for bearer-token API routes).
|
|
@@ -11,6 +11,7 @@ import { validateLogoutToken } from '../src/backchannel_logout.js';
|
|
|
11
11
|
import { conventionEndpoints, discoverEndpoints, mergeEndpoints, } from '../src/discovery.js';
|
|
12
12
|
import { validateIdToken, } from '../src/id_token.js';
|
|
13
13
|
import { getJwks } from '../src/jwks.js';
|
|
14
|
+
import { contextTenancyEnabled } from '../src/observability/context_bridge.js';
|
|
14
15
|
import { exchangeToken, refreshTokens } from '../src/oidc_login.js';
|
|
15
16
|
/** Margem (ms) antes do `expiresAt` em que o access token é renovado proativamente. */
|
|
16
17
|
const REFRESH_SKEW_MS = 60_000;
|
|
@@ -457,6 +458,18 @@ export class AuthkitClientManager {
|
|
|
457
458
|
session.forget(this.#impersonatorKey);
|
|
458
459
|
session.put(this.config.sessionKey, tokenSet);
|
|
459
460
|
}
|
|
461
|
+
/**
|
|
462
|
+
* Whether `authkit_context_middleware` resolves the session at request start, so
|
|
463
|
+
* the tenant is in `@adonis-agora/context` before any handler runs. `tenancy.eager`
|
|
464
|
+
* when set; otherwise `true` only when context tenancy is enabled and the resolver
|
|
465
|
+
* is `resolvers.jwt()` (local verify, no network).
|
|
466
|
+
*/
|
|
467
|
+
eagerTenancy() {
|
|
468
|
+
const eager = this.config.tenancy?.eager;
|
|
469
|
+
if (typeof eager === 'boolean')
|
|
470
|
+
return eager;
|
|
471
|
+
return this.config.resolverFactory?.kind === 'jwt' && contextTenancyEnabled();
|
|
472
|
+
}
|
|
460
473
|
/**
|
|
461
474
|
* The request's {@link Authenticator}. `options.resolver` replaces the configured
|
|
462
475
|
* resolver for this authenticator only (e.g. a guard for bearer-token API routes).
|
|
@@ -467,6 +480,7 @@ export class AuthkitClientManager {
|
|
|
467
480
|
return new Authenticator(ctx, {
|
|
468
481
|
resolver,
|
|
469
482
|
resolveUser: this.config.resolveUser,
|
|
483
|
+
tenancy: this.config.tenancy,
|
|
470
484
|
getAccessToken: () => {
|
|
471
485
|
const tokenSet = ctx.session?.get(sessionKey);
|
|
472
486
|
return tokenSet?.accessToken;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Identity, SessionResolver } from '@adonis-agora/authkit-core';
|
|
2
2
|
import { type MetricsRecorder } from '@adonis-agora/authkit-core';
|
|
3
3
|
import type { HttpContext } from '@adonisjs/core/http';
|
|
4
|
+
import { type ClientTenancyOptions } from './observability/context_bridge.js';
|
|
4
5
|
/** Contexto opcional repassado a `resolveUser` (extensão backward-compatible). */
|
|
5
6
|
export interface ResolveUserContext {
|
|
6
7
|
accessToken?: string;
|
|
@@ -10,6 +11,8 @@ export interface AuthenticatorDeps {
|
|
|
10
11
|
resolveUser?: (identity: Identity, context: ResolveUserContext) => Promise<unknown>;
|
|
11
12
|
/** lê o access token do token set da sessão (opcional; usado por resolvers userinfo) */
|
|
12
13
|
getAccessToken?: () => string | null | undefined;
|
|
14
|
+
/** Opções de tenancy (`config/authkit_client.ts` → `tenancy`) usadas ao publicar o tenant. */
|
|
15
|
+
tenancy?: ClientTenancyOptions;
|
|
13
16
|
}
|
|
14
17
|
/**
|
|
15
18
|
* Autenticador do request. O parâmetro `TUser` é o tipo do usuário do app que `resolveUser`
|
|
@@ -36,7 +36,7 @@ export class Authenticator {
|
|
|
36
36
|
// (best-effort, no-op sem o slot `@agora/context:set`). Apenas no caminho
|
|
37
37
|
// de sucesso — não força resolução para requests anônimas.
|
|
38
38
|
if (this.#identity)
|
|
39
|
-
populateContext(this.#identity);
|
|
39
|
+
populateContext(this.#identity, this.deps.tenancy);
|
|
40
40
|
}
|
|
41
41
|
return this.#identity;
|
|
42
42
|
}
|
|
@@ -2,6 +2,7 @@ import type { Identity } from '@adonis-agora/authkit-core';
|
|
|
2
2
|
import type { SessionIndex } from './backchannel_logout.js';
|
|
3
3
|
import type { OidcEndpoints } from './discovery.js';
|
|
4
4
|
import type { ResiliencePolicy } from './http/resilient_fetch.js';
|
|
5
|
+
import type { ClientTenancyOptions } from './observability/context_bridge.js';
|
|
5
6
|
import { type ResolverFactory, resolvers } from './resolvers/factory.js';
|
|
6
7
|
import type { RevocationStore } from './revocation/revocation_store.js';
|
|
7
8
|
export { resolvers };
|
|
@@ -76,6 +77,12 @@ export interface ClientConfigInput {
|
|
|
76
77
|
};
|
|
77
78
|
/** Per-field endpoint overrides; always win over discovery. */
|
|
78
79
|
endpoints?: Partial<OidcEndpoints>;
|
|
80
|
+
/**
|
|
81
|
+
* How the session's tenant is published into `@adonis-agora/context` (the active
|
|
82
|
+
* organization by default). See {@link ClientTenancyOptions}: `eager`, `personal`,
|
|
83
|
+
* `resolve`. No effect without `@adonis-agora/context`.
|
|
84
|
+
*/
|
|
85
|
+
tenancy?: ClientTenancyOptions;
|
|
79
86
|
}
|
|
80
87
|
export interface ResolvedClientConfig {
|
|
81
88
|
issuer: string;
|
|
@@ -101,5 +108,7 @@ export interface ResolvedClientConfig {
|
|
|
101
108
|
};
|
|
102
109
|
/** Endpoint overrides. See {@link ClientConfigInput.endpoints}. */
|
|
103
110
|
endpoints?: Partial<OidcEndpoints>;
|
|
111
|
+
/** Tenancy options. See {@link ClientConfigInput.tenancy}. */
|
|
112
|
+
tenancy?: ClientTenancyOptions;
|
|
104
113
|
}
|
|
105
114
|
export declare function defineConfig(config: ClientConfigInput): import("@adonisjs/core/types").ConfigProvider<ResolvedClientConfig>;
|
|
@@ -21,6 +21,11 @@ import type { NextFn } from '@adonisjs/core/types/http';
|
|
|
21
21
|
*
|
|
22
22
|
* Sem `@adonisjs/auth`, é ele sozinho que dá a sessão ao app: `ctx.authkit`
|
|
23
23
|
* em qualquer rota, e `auth_middleware` para exigir login.
|
|
24
|
+
*
|
|
25
|
+
* With tenancy eager (`tenancy.eager`, default on when `config/context.ts` enables
|
|
26
|
+
* tenancy and the resolver is `jwt`), it also resolves the session here, so the
|
|
27
|
+
* request's tenant is in `@adonis-agora/context` before any handler, job dispatch
|
|
28
|
+
* or console query runs.
|
|
24
29
|
*/
|
|
25
30
|
export default class AuthkitContextMiddleware {
|
|
26
31
|
handle(ctx: HttpContext, next: NextFn): Promise<any>;
|
|
@@ -22,10 +22,27 @@ import { getAuthkit } from '../host/request_authenticator.js';
|
|
|
22
22
|
*
|
|
23
23
|
* Sem `@adonisjs/auth`, é ele sozinho que dá a sessão ao app: `ctx.authkit`
|
|
24
24
|
* em qualquer rota, e `auth_middleware` para exigir login.
|
|
25
|
+
*
|
|
26
|
+
* With tenancy eager (`tenancy.eager`, default on when `config/context.ts` enables
|
|
27
|
+
* tenancy and the resolver is `jwt`), it also resolves the session here, so the
|
|
28
|
+
* request's tenant is in `@adonis-agora/context` before any handler, job dispatch
|
|
29
|
+
* or console query runs.
|
|
25
30
|
*/
|
|
26
31
|
export default class AuthkitContextMiddleware {
|
|
27
32
|
async handle(ctx, next) {
|
|
28
|
-
await getAuthkit(ctx);
|
|
33
|
+
const authkit = await getAuthkit(ctx);
|
|
34
|
+
if (await eagerTenancy(ctx)) {
|
|
35
|
+
try {
|
|
36
|
+
await authkit.getIdentity();
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Same as lazy: the failure surfaces when the app asks for the session.
|
|
40
|
+
}
|
|
41
|
+
}
|
|
29
42
|
return next();
|
|
30
43
|
}
|
|
31
44
|
}
|
|
45
|
+
async function eagerTenancy(ctx) {
|
|
46
|
+
const manager = (await ctx.containerResolver.make('authkit.client'));
|
|
47
|
+
return typeof manager.eagerTenancy === 'function' && manager.eagerTenancy();
|
|
48
|
+
}
|
|
@@ -1,8 +1,64 @@
|
|
|
1
1
|
import type { Identity } from '@adonis-agora/authkit-core';
|
|
2
|
+
/**
|
|
3
|
+
* The tenant authkit publishes into `@agora/context` (structural copy of the
|
|
4
|
+
* context's `TenantRef`). `chain` lists the active path from the outermost
|
|
5
|
+
* tenant to this one (this one last) when tenants nest; absent for a flat tenant.
|
|
6
|
+
*/
|
|
7
|
+
export interface ContextTenant {
|
|
8
|
+
id: string;
|
|
9
|
+
kind: 'organization' | 'user' | (string & {});
|
|
10
|
+
source: 'authkit' | 'app' | 'carrier' | 'legacy' | (string & {});
|
|
11
|
+
slug?: string;
|
|
12
|
+
role?: string;
|
|
13
|
+
chain?: ContextTenantLink[];
|
|
14
|
+
}
|
|
15
|
+
/** One level of a nested tenant chain. */
|
|
16
|
+
export interface ContextTenantLink {
|
|
17
|
+
id: string;
|
|
18
|
+
kind: string;
|
|
19
|
+
slug?: string;
|
|
20
|
+
role?: string;
|
|
21
|
+
}
|
|
22
|
+
/** What `tenancy.resolve` may return: one tenant, a chain (outermost first, active last), or none. */
|
|
23
|
+
export type TenantInput = Omit<ContextTenantLink, 'kind'> & {
|
|
24
|
+
kind?: string;
|
|
25
|
+
};
|
|
26
|
+
/** Client-side tenancy options (`config/authkit_client.ts` → `tenancy`). */
|
|
27
|
+
export interface ClientTenancyOptions {
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the session (and so publish the tenant) at the start of every request
|
|
30
|
+
* that runs `authkit_context_middleware`, instead of on first use. Default: `true`
|
|
31
|
+
* when `config/context.ts` enables tenancy AND the resolver is `resolvers.jwt()`
|
|
32
|
+
* (local verify, cheap); `false` otherwise.
|
|
33
|
+
*/
|
|
34
|
+
eager?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* With no active organization, publish the user as the tenant (`kind: 'user'`),
|
|
37
|
+
* so personal workspaces are tenants too. Default: `false` (orgless requests
|
|
38
|
+
* have no tenant).
|
|
39
|
+
*/
|
|
40
|
+
personal?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Map the session to your own tenant model (e.g. clinic → unit). Return one
|
|
43
|
+
* tenant, a chain (outermost first, active last), or `null` for no tenant;
|
|
44
|
+
* return `undefined` to keep authkit's default (the active organization).
|
|
45
|
+
*/
|
|
46
|
+
resolve?: (identity: Identity) => TenantInput | TenantInput[] | null | undefined;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* `true` when the installed `@adonis-agora/context` has tenancy enabled
|
|
50
|
+
* (`config/context.ts` → `tenancy.enabled`). `false` without context, or with a
|
|
51
|
+
* context version that predates tenancy.
|
|
52
|
+
*/
|
|
53
|
+
export declare function contextTenancyEnabled(): boolean;
|
|
54
|
+
/** The tenant authkit would publish for `identity` under `options`. */
|
|
55
|
+
export declare function tenantFor(identity: Identity, options?: ClientTenancyOptions): ContextTenant | null;
|
|
2
56
|
/**
|
|
3
57
|
* Popula o contexto do Agora a partir de uma identidade recém-resolvida:
|
|
4
58
|
* `userRef` ({ type: 'user', id: userId }), os `globalRoles` da identidade e,
|
|
5
|
-
* quando
|
|
6
|
-
*
|
|
59
|
+
* quando há um tenant, `tenant` (id, kind, slug, role, e `chain` quando aninhado)
|
|
60
|
+
* mais `tenantId`. Um tenant que o próprio app já definiu nesta request
|
|
61
|
+
* (`Context.setTenant`, `source: 'app'`) nunca é sobrescrito. Best-effort:
|
|
62
|
+
* qualquer erro é engolido e nada acontece sem o slot.
|
|
7
63
|
*/
|
|
8
|
-
export declare function populateContext(identity: Identity): void;
|
|
64
|
+
export declare function populateContext(identity: Identity, options?: ClientTenancyOptions): void;
|
|
@@ -1,11 +1,44 @@
|
|
|
1
|
-
import { deriveOrgId } from '@adonis-agora/authkit-core';
|
|
1
|
+
import { deriveOrgId, deriveOrgRole, deriveOrgSlug } from '@adonis-agora/authkit-core';
|
|
2
2
|
/**
|
|
3
3
|
* Ponte estrutural (best-effort) entre a sessão resolvida do authkit e o
|
|
4
|
-
* contexto do Agora. Lê
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* contexto do Agora. Lê os slots globais `@agora/context:set` e
|
|
5
|
+
* `@agora/context:accessor` de forma ESTRUTURAL — nunca importa
|
|
6
|
+
* `@adonis-agora/context` — e degrada para no-op quando o context não está
|
|
7
|
+
* instalado (ou é uma versão sem tenancy). Mesmo idioma da `diagnostics_bridge`.
|
|
7
8
|
*/
|
|
8
9
|
const SET_SLOT = Symbol.for('@agora/context:set');
|
|
10
|
+
const ACCESSOR_SLOT = Symbol.for('@agora/context:accessor');
|
|
11
|
+
function accessor() {
|
|
12
|
+
const value = globalThis[ACCESSOR_SLOT];
|
|
13
|
+
return value && typeof value === 'object' ? value : undefined;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* `true` when the installed `@adonis-agora/context` has tenancy enabled
|
|
17
|
+
* (`config/context.ts` → `tenancy.enabled`). `false` without context, or with a
|
|
18
|
+
* context version that predates tenancy.
|
|
19
|
+
*/
|
|
20
|
+
export function contextTenancyEnabled() {
|
|
21
|
+
try {
|
|
22
|
+
const tenancy = accessor()?.tenancy;
|
|
23
|
+
return typeof tenancy === 'function' && tenancy()?.enabled === true;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** The tenant an app set itself (`Context.setTenant`) on this request, if any. */
|
|
30
|
+
function appTenantAlreadySet() {
|
|
31
|
+
try {
|
|
32
|
+
const get = accessor()?.get;
|
|
33
|
+
if (typeof get !== 'function')
|
|
34
|
+
return false;
|
|
35
|
+
const current = get('tenant');
|
|
36
|
+
return !!current && current.source === 'app';
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
9
42
|
/**
|
|
10
43
|
* Deriva o tenant ativo da identidade. Prefere o `orgId` já resolvido (o caminho
|
|
11
44
|
* normal — `buildIdentityFromClaims` o preenche); cai para as claims cruas
|
|
@@ -13,25 +46,80 @@ const SET_SLOT = Symbol.for('@agora/context:set');
|
|
|
13
46
|
* populou. A tabela de claims aceitas vive em `@adonis-agora/authkit-core`
|
|
14
47
|
* (`ORG_ID_CLAIMS`) para não divergir entre a identidade e esta ponte.
|
|
15
48
|
*/
|
|
16
|
-
function
|
|
17
|
-
|
|
49
|
+
function organizationTenant(identity) {
|
|
50
|
+
const id = identity.orgId ?? deriveOrgId(identity.raw);
|
|
51
|
+
if (!id)
|
|
52
|
+
return null;
|
|
53
|
+
const slug = identity.orgSlug ?? deriveOrgSlug(identity.raw);
|
|
54
|
+
const role = identity.orgRole ?? deriveOrgRole(identity.raw);
|
|
55
|
+
return {
|
|
56
|
+
id,
|
|
57
|
+
kind: 'organization',
|
|
58
|
+
source: 'authkit',
|
|
59
|
+
...(slug ? { slug } : {}),
|
|
60
|
+
...(role ? { role } : {}),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function link(input, fallbackKind) {
|
|
64
|
+
if (!input || typeof input.id !== 'string' || input.id === '')
|
|
65
|
+
return null;
|
|
66
|
+
return {
|
|
67
|
+
id: input.id,
|
|
68
|
+
kind: typeof input.kind === 'string' && input.kind !== '' ? input.kind : fallbackKind,
|
|
69
|
+
...(typeof input.slug === 'string' && input.slug ? { slug: input.slug } : {}),
|
|
70
|
+
...(typeof input.role === 'string' && input.role ? { role: input.role } : {}),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function fromResolved(resolved) {
|
|
74
|
+
const inputs = Array.isArray(resolved) ? resolved : [resolved];
|
|
75
|
+
const links = inputs.map((input) => link(input, 'organization'));
|
|
76
|
+
if (links.length === 0 || links.some((l) => l === null))
|
|
77
|
+
return null;
|
|
78
|
+
const chain = links;
|
|
79
|
+
const active = chain[chain.length - 1];
|
|
80
|
+
return { ...active, source: 'authkit', ...(chain.length > 1 ? { chain } : {}) };
|
|
81
|
+
}
|
|
82
|
+
/** The tenant authkit would publish for `identity` under `options`. */
|
|
83
|
+
export function tenantFor(identity, options = {}) {
|
|
84
|
+
if (options.resolve) {
|
|
85
|
+
const resolved = options.resolve(identity);
|
|
86
|
+
if (resolved === null)
|
|
87
|
+
return null;
|
|
88
|
+
if (resolved !== undefined)
|
|
89
|
+
return fromResolved(resolved);
|
|
90
|
+
}
|
|
91
|
+
const org = organizationTenant(identity);
|
|
92
|
+
if (org)
|
|
93
|
+
return org;
|
|
94
|
+
if (options.personal === true && identity.userId) {
|
|
95
|
+
return { id: identity.userId, kind: 'user', source: 'authkit' };
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
18
98
|
}
|
|
19
99
|
/**
|
|
20
100
|
* Popula o contexto do Agora a partir de uma identidade recém-resolvida:
|
|
21
101
|
* `userRef` ({ type: 'user', id: userId }), os `globalRoles` da identidade e,
|
|
22
|
-
* quando
|
|
23
|
-
*
|
|
102
|
+
* quando há um tenant, `tenant` (id, kind, slug, role, e `chain` quando aninhado)
|
|
103
|
+
* mais `tenantId`. Um tenant que o próprio app já definiu nesta request
|
|
104
|
+
* (`Context.setTenant`, `source: 'app'`) nunca é sobrescrito. Best-effort:
|
|
105
|
+
* qualquer erro é engolido e nada acontece sem o slot.
|
|
24
106
|
*/
|
|
25
|
-
export function populateContext(identity) {
|
|
107
|
+
export function populateContext(identity, options = {}) {
|
|
26
108
|
try {
|
|
27
109
|
const set = globalThis[SET_SLOT];
|
|
28
110
|
if (!set)
|
|
29
111
|
return;
|
|
30
|
-
|
|
112
|
+
let tenant = null;
|
|
113
|
+
try {
|
|
114
|
+
tenant = appTenantAlreadySet() ? null : tenantFor(identity, options);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
tenant = null;
|
|
118
|
+
}
|
|
31
119
|
set({
|
|
32
120
|
userRef: { type: 'user', id: identity.userId },
|
|
33
121
|
globalRoles: identity.globalRoles,
|
|
34
|
-
...(tenant ? { tenantId: tenant } : {}),
|
|
122
|
+
...(tenant ? { tenantId: tenant.id, tenant } : {}),
|
|
35
123
|
});
|
|
36
124
|
}
|
|
37
125
|
catch {
|
|
@@ -45,6 +45,8 @@ export interface ResolverContext {
|
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
export interface ResolverFactory {
|
|
48
|
+
/** Which built-in resolver this is (`jwt`, `opaque`, `pat`); absent for a custom one. */
|
|
49
|
+
readonly kind?: 'jwt' | 'opaque' | 'pat' | (string & {});
|
|
48
50
|
resolver(ctx: ResolverContext): Promise<SessionResolver>;
|
|
49
51
|
}
|
|
50
52
|
export declare const resolvers: {
|
|
@@ -23,6 +23,7 @@ export const resolvers = {
|
|
|
23
23
|
jwt(config = {}) {
|
|
24
24
|
const tokenSource = config.tokenSource ?? 'session';
|
|
25
25
|
return {
|
|
26
|
+
kind: 'jwt',
|
|
26
27
|
async resolver(rc) {
|
|
27
28
|
return new JwtResolver({
|
|
28
29
|
endpoints: async () => {
|
|
@@ -41,6 +42,7 @@ export const resolvers = {
|
|
|
41
42
|
},
|
|
42
43
|
pat(config) {
|
|
43
44
|
return {
|
|
45
|
+
kind: 'pat',
|
|
44
46
|
async resolver(_rc) {
|
|
45
47
|
return new PatResolver({
|
|
46
48
|
introspectionUrl: config.introspectionUrl,
|
|
@@ -52,6 +54,7 @@ export const resolvers = {
|
|
|
52
54
|
opaque(config = {}) {
|
|
53
55
|
const tokenSource = config.tokenSource ?? 'session';
|
|
54
56
|
return {
|
|
57
|
+
kind: 'opaque',
|
|
55
58
|
async resolver(rc) {
|
|
56
59
|
if (!rc.clientSecret) {
|
|
57
60
|
throw new Error('resolvers.opaque requer um client confidencial (clientSecret) para introspection');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adonis-agora/authkit-client",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.4",
|
|
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",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"jose": "6.2.16"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
|
-
"@adonis-agora/authkit-server": "1.0.0-rc.
|
|
84
|
+
"@adonis-agora/authkit-server": "1.0.0-rc.7",
|
|
85
85
|
"@adonis-agora/resilience": "0.4.3",
|
|
86
86
|
"@adonisjs/auth": "10.1.0",
|
|
87
87
|
"@adonisjs/core": "7.6.0",
|