@adonis-agora/authkit-client 0.17.4 → 0.18.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.
- package/build/index.d.ts +2 -0
- package/build/index.js +1 -0
- package/build/src/middleware/require_org_middleware.d.ts +47 -0
- package/build/src/middleware/require_org_middleware.js +39 -0
- package/build/src/observability/context_bridge.js +9 -20
- package/build/src/resolvers/identity.d.ts +3 -1
- package/build/src/resolvers/identity.js +7 -1
- package/package.json +4 -3
package/build/index.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export { getAuthkit } from './src/host/request_authenticator.js';
|
|
|
19
19
|
export { isAuthkitAdmin, authkitDashboardAuthorize, authkitDashboardMiddleware, } from './src/host/dashboard_guard.js';
|
|
20
20
|
export type { DashboardGuardOptions } from './src/host/dashboard_guard.js';
|
|
21
21
|
export { default as AuthkitContextMiddleware } from './src/middleware/authkit_context_middleware.js';
|
|
22
|
+
export { default as RequireOrgMiddleware } from './src/middleware/require_org_middleware.js';
|
|
23
|
+
export type { RequireOrgMiddlewareOptions } from './src/middleware/require_org_middleware.js';
|
|
22
24
|
export { lucidMirror } from './src/lucid_mirror.js';
|
|
23
25
|
export type { LucidMirrorOptions } from './src/lucid_mirror.js';
|
|
24
26
|
export { registerOidcClient } from './src/register_oidc_client.js';
|
package/build/index.js
CHANGED
|
@@ -11,6 +11,7 @@ export { authkitClientGuard, AuthkitClientGuard } from './src/host/authkit_clien
|
|
|
11
11
|
export { getAuthkit } from './src/host/request_authenticator.js';
|
|
12
12
|
export { isAuthkitAdmin, authkitDashboardAuthorize, authkitDashboardMiddleware, } from './src/host/dashboard_guard.js';
|
|
13
13
|
export { default as AuthkitContextMiddleware } from './src/middleware/authkit_context_middleware.js';
|
|
14
|
+
export { default as RequireOrgMiddleware } from './src/middleware/require_org_middleware.js';
|
|
14
15
|
export { lucidMirror } from './src/lucid_mirror.js';
|
|
15
16
|
export { registerOidcClient } from './src/register_oidc_client.js';
|
|
16
17
|
export { validateLogoutToken, InvalidLogoutTokenError, InMemorySessionIndex, BACKCHANNEL_LOGOUT_EVENT, } from './src/backchannel_logout.js';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
2
|
+
import type { NextFn } from '@adonisjs/core/types/http';
|
|
3
|
+
export interface RequireOrgMiddlewareOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Para onde mandar quem não tem org ativa. Default: `/account/orgs` — a tela
|
|
6
|
+
* de seleção que o host-kit do authkit-server já serve.
|
|
7
|
+
*/
|
|
8
|
+
redirectTo?: string;
|
|
9
|
+
/**
|
|
10
|
+
* `web` (default) redireciona para {@link redirectTo}; `api` responde 403 com
|
|
11
|
+
* um corpo JSON — rotas de API não têm para onde redirecionar.
|
|
12
|
+
*/
|
|
13
|
+
mode?: 'web' | 'api';
|
|
14
|
+
/**
|
|
15
|
+
* Restringe às orgs listadas. Útil para rotas que só existem dentro de um
|
|
16
|
+
* conjunto conhecido de tenants. Ausente → qualquer org ativa serve.
|
|
17
|
+
*/
|
|
18
|
+
oneOf?: string[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Exige que a sessão tenha uma **organização ativa** (claim `org_id`, exposta
|
|
22
|
+
* como `identity.orgId`).
|
|
23
|
+
*
|
|
24
|
+
* Existe porque num app multi-tenant "estar autenticado" e "estar dentro de um
|
|
25
|
+
* tenant" são coisas diferentes: sem esta barreira, uma request de um usuário
|
|
26
|
+
* logado mas sem org ativa chega no controller com `tenantId` indefinido, e daí
|
|
27
|
+
* o escopo por tenant do `@adonis-agora/authz` cai no escopo GLOBAL — que é o
|
|
28
|
+
* pior lugar para se cair por acidente. Fail-closed: sem sessão ou sem org, não
|
|
29
|
+
* passa.
|
|
30
|
+
*
|
|
31
|
+
* Registre como named middleware e encadeie DEPOIS do de auth:
|
|
32
|
+
*
|
|
33
|
+
* ```ts title="start/kernel.ts"
|
|
34
|
+
* export const middleware = router.named({
|
|
35
|
+
* auth: () => import('@adonis-agora/authkit-client/auth_middleware'),
|
|
36
|
+
* requireOrg: () => import('@adonis-agora/authkit-client/require_org_middleware'),
|
|
37
|
+
* })
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* ```ts title="start/routes.ts"
|
|
41
|
+
* router.group(() => { … }).use([middleware.auth(), middleware.requireOrg()])
|
|
42
|
+
* router.group(() => { … }).use([middleware.auth(), middleware.requireOrg({ mode: 'api' })])
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export default class RequireOrgMiddleware {
|
|
46
|
+
handle(ctx: HttpContext, next: NextFn, options?: RequireOrgMiddlewareOptions): Promise<any>;
|
|
47
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ctxAuth } from '../host/ctx_auth.js';
|
|
2
|
+
/**
|
|
3
|
+
* Exige que a sessão tenha uma **organização ativa** (claim `org_id`, exposta
|
|
4
|
+
* como `identity.orgId`).
|
|
5
|
+
*
|
|
6
|
+
* Existe porque num app multi-tenant "estar autenticado" e "estar dentro de um
|
|
7
|
+
* tenant" são coisas diferentes: sem esta barreira, uma request de um usuário
|
|
8
|
+
* logado mas sem org ativa chega no controller com `tenantId` indefinido, e daí
|
|
9
|
+
* o escopo por tenant do `@adonis-agora/authz` cai no escopo GLOBAL — que é o
|
|
10
|
+
* pior lugar para se cair por acidente. Fail-closed: sem sessão ou sem org, não
|
|
11
|
+
* passa.
|
|
12
|
+
*
|
|
13
|
+
* Registre como named middleware e encadeie DEPOIS do de auth:
|
|
14
|
+
*
|
|
15
|
+
* ```ts title="start/kernel.ts"
|
|
16
|
+
* export const middleware = router.named({
|
|
17
|
+
* auth: () => import('@adonis-agora/authkit-client/auth_middleware'),
|
|
18
|
+
* requireOrg: () => import('@adonis-agora/authkit-client/require_org_middleware'),
|
|
19
|
+
* })
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* ```ts title="start/routes.ts"
|
|
23
|
+
* router.group(() => { … }).use([middleware.auth(), middleware.requireOrg()])
|
|
24
|
+
* router.group(() => { … }).use([middleware.auth(), middleware.requireOrg({ mode: 'api' })])
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export default class RequireOrgMiddleware {
|
|
28
|
+
async handle(ctx, next, options = {}) {
|
|
29
|
+
const identity = await ctxAuth(ctx).getIdentity();
|
|
30
|
+
const orgId = identity?.orgId ?? null;
|
|
31
|
+
const allowed = orgId !== null && (!options.oneOf || options.oneOf.includes(orgId));
|
|
32
|
+
if (allowed)
|
|
33
|
+
return next();
|
|
34
|
+
if (options.mode === 'api') {
|
|
35
|
+
return ctx.response.abort({ error: 'no_active_organization', message: 'This route requires an active organization.' }, 403);
|
|
36
|
+
}
|
|
37
|
+
return ctx.response.redirect(options.redirectTo ?? '/account/orgs');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { deriveOrgId } from '@adonis-agora/authkit-core';
|
|
1
2
|
/**
|
|
2
3
|
* Ponte estrutural (best-effort) entre a sessão resolvida do authkit e o
|
|
3
4
|
* contexto do Agora. Lê o slot global `@agora/context:set` de forma ESTRUTURAL —
|
|
@@ -6,26 +7,14 @@
|
|
|
6
7
|
*/
|
|
7
8
|
const SET_SLOT = Symbol.for('@agora/context:set');
|
|
8
9
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
10
|
+
* Deriva o tenant ativo da identidade. Prefere o `orgId` já resolvido (o caminho
|
|
11
|
+
* normal — `buildIdentityFromClaims` o preenche); cai para as claims cruas
|
|
12
|
+
* quando a `Identity` veio de um `SessionResolver` customizado que não o
|
|
13
|
+
* populou. A tabela de claims aceitas vive em `@adonis-agora/authkit-core`
|
|
14
|
+
* (`ORG_ID_CLAIMS`) para não divergir entre a identidade e esta ponte.
|
|
11
15
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
'org_id',
|
|
15
|
-
'organization_id',
|
|
16
|
-
'tenant_id',
|
|
17
|
-
'tid',
|
|
18
|
-
];
|
|
19
|
-
/** Deriva o tenant ativo das claims cruas, ou `undefined` quando ausente. */
|
|
20
|
-
function deriveTenant(raw) {
|
|
21
|
-
if (!raw)
|
|
22
|
-
return undefined;
|
|
23
|
-
for (const claim of TENANT_CLAIMS) {
|
|
24
|
-
const value = raw[claim];
|
|
25
|
-
if (typeof value === 'string' && value.length > 0)
|
|
26
|
-
return value;
|
|
27
|
-
}
|
|
28
|
-
return undefined;
|
|
16
|
+
function deriveTenant(identity) {
|
|
17
|
+
return identity.orgId ?? deriveOrgId(identity.raw) ?? undefined;
|
|
29
18
|
}
|
|
30
19
|
/**
|
|
31
20
|
* Popula o contexto do Agora a partir de uma identidade recém-resolvida:
|
|
@@ -38,7 +27,7 @@ export function populateContext(identity) {
|
|
|
38
27
|
const set = globalThis[SET_SLOT];
|
|
39
28
|
if (!set)
|
|
40
29
|
return;
|
|
41
|
-
const tenant = deriveTenant(identity
|
|
30
|
+
const tenant = deriveTenant(identity);
|
|
42
31
|
set({
|
|
43
32
|
userRef: { type: 'user', id: identity.userId },
|
|
44
33
|
globalRoles: identity.globalRoles,
|
|
@@ -8,7 +8,9 @@ type FetchImpl = (url: string, init: any) => Promise<{
|
|
|
8
8
|
* resolvers `jwt`, `pat` e `opaque`. Espelha exatamente o que o `JwtResolver`
|
|
9
9
|
* produzia (a referência canônica): `sub`→userId, `email`, papéis na
|
|
10
10
|
* `globalRolesClaim`, `name`/`picture`→profile, `sid`→sessionId, `iat`/`exp`
|
|
11
|
-
* (segundos→ms, fallback 0)
|
|
11
|
+
* (segundos→ms, fallback 0), a org/tenant ativo (`orgId`/`orgSlug`/`orgRole`,
|
|
12
|
+
* ver `deriveOrgId` — vale para o authkit E para IdPs de terceiros) e as claims
|
|
13
|
+
* cruas em `raw`.
|
|
12
14
|
*/
|
|
13
15
|
export declare function buildIdentityFromClaims(claims: Record<string, unknown>, globalRolesClaim: string): Identity;
|
|
14
16
|
export interface IntrospectAuth {
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { deriveOrgId, deriveOrgRole, deriveOrgSlug } from '@adonis-agora/authkit-core';
|
|
1
2
|
/**
|
|
2
3
|
* Mapeamento canônico de claims OIDC validadas → Identity, compartilhado pelos
|
|
3
4
|
* resolvers `jwt`, `pat` e `opaque`. Espelha exatamente o que o `JwtResolver`
|
|
4
5
|
* produzia (a referência canônica): `sub`→userId, `email`, papéis na
|
|
5
6
|
* `globalRolesClaim`, `name`/`picture`→profile, `sid`→sessionId, `iat`/`exp`
|
|
6
|
-
* (segundos→ms, fallback 0)
|
|
7
|
+
* (segundos→ms, fallback 0), a org/tenant ativo (`orgId`/`orgSlug`/`orgRole`,
|
|
8
|
+
* ver `deriveOrgId` — vale para o authkit E para IdPs de terceiros) e as claims
|
|
9
|
+
* cruas em `raw`.
|
|
7
10
|
*/
|
|
8
11
|
export function buildIdentityFromClaims(claims, globalRolesClaim) {
|
|
9
12
|
const roles = claims[globalRolesClaim];
|
|
@@ -16,6 +19,9 @@ export function buildIdentityFromClaims(claims, globalRolesClaim) {
|
|
|
16
19
|
avatarUrl: typeof claims.picture === 'string' ? claims.picture : undefined,
|
|
17
20
|
},
|
|
18
21
|
sessionId: typeof claims.sid === 'string' ? claims.sid : undefined,
|
|
22
|
+
orgId: deriveOrgId(claims),
|
|
23
|
+
orgSlug: deriveOrgSlug(claims),
|
|
24
|
+
orgRole: deriveOrgRole(claims),
|
|
19
25
|
issuedAt: typeof claims.iat === 'number' ? claims.iat * 1000 : 0,
|
|
20
26
|
expiresAt: typeof claims.exp === 'number' ? claims.exp * 1000 : 0,
|
|
21
27
|
raw: claims,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adonis-agora/authkit-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.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",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"./authkit_context_middleware": "./build/src/middleware/authkit_context_middleware.js",
|
|
46
46
|
"./backchannel_revocation_middleware": "./build/src/middleware/backchannel_revocation_middleware.js",
|
|
47
47
|
"./auth_middleware": "./build/src/middleware/auth_middleware.js",
|
|
48
|
+
"./require_org_middleware": "./build/src/middleware/require_org_middleware.js",
|
|
48
49
|
"./silent_auth_middleware": "./build/src/middleware/silent_auth_middleware.js",
|
|
49
50
|
"./commands": "./build/commands/configure.js"
|
|
50
51
|
},
|
|
@@ -77,7 +78,7 @@
|
|
|
77
78
|
},
|
|
78
79
|
"dependencies": {
|
|
79
80
|
"jose": "5.10.0",
|
|
80
|
-
"@adonis-agora/authkit-core": "0.
|
|
81
|
+
"@adonis-agora/authkit-core": "0.8.0"
|
|
81
82
|
},
|
|
82
83
|
"devDependencies": {
|
|
83
84
|
"@adonisjs/auth": "10.1.0",
|
|
@@ -92,7 +93,7 @@
|
|
|
92
93
|
"ioredis-mock": "8.13.1",
|
|
93
94
|
"@types/node": "25.9.1",
|
|
94
95
|
"typescript": "6.0.3",
|
|
95
|
-
"@adonis-agora/authkit-server": "0.
|
|
96
|
+
"@adonis-agora/authkit-server": "0.59.0"
|
|
96
97
|
},
|
|
97
98
|
"scripts": {
|
|
98
99
|
"build": "tsc && node -e \"require('node:fs').cpSync('stubs','build/stubs',{recursive:true,filter:(s)=>!s.endsWith('.ts')})\"",
|