@adonis-agora/authkit-server 0.90.2 → 1.0.0-rc.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/CHANGELOG.md +3637 -0
- package/build/commands/clients_create.d.ts +1 -0
- package/build/commands/clients_create.js +15 -2
- package/build/commands/commands.json +7 -0
- package/build/host/ui/admin.html +1 -1
- package/build/host/views/account/apps.edge +8 -3
- package/build/host/views/partials/styles.edge +1 -1
- package/build/index.d.ts +3 -3
- package/build/index.js +3 -5
- package/build/src/accounts/lucid_account_store.d.ts +0 -18
- package/build/src/accounts/lucid_account_store.js +7 -48
- package/build/src/accounts/lucid_store/mfa.js +1 -1
- package/build/src/accounts/lucid_store/organizations.d.ts +1 -1
- package/build/src/accounts/lucid_store/password_hygiene.d.ts +2 -3
- package/build/src/accounts/lucid_store/password_hygiene.js +2 -3
- package/build/src/accounts/lucid_store/shared.d.ts +0 -7
- package/build/src/accounts/lucid_store/shared.js +0 -14
- package/build/src/accounts/lucid_stores.d.ts +1 -1
- package/build/src/adapters/redis_adapter.js +12 -2
- package/build/src/define_config.d.ts +6 -12
- package/build/src/define_config.js +3 -2
- package/build/src/doctor/checks.d.ts +3 -4
- package/build/src/doctor/checks.js +5 -7
- package/build/src/host/account_api/account_api_controller.d.ts +2 -1
- package/build/src/host/account_api/account_api_controller.js +5 -11
- package/build/src/host/admin_api/api_clients_controller.d.ts +9 -3
- package/build/src/host/admin_api/dto.d.ts +3 -1
- package/build/src/host/admin_api/dto.js +3 -1
- package/build/src/host/admin_clients_service.d.ts +13 -5
- package/build/src/host/admin_clients_service.js +18 -5
- package/build/src/host/admin_console/admin_shell_controller.d.ts +16 -1
- package/build/src/host/admin_console/admin_shell_controller.js +21 -7
- package/build/src/host/admin_console/console_clients_controller.d.ts +21 -2
- package/build/src/host/admin_console/console_clients_controller.js +9 -0
- package/build/src/host/admin_console/console_impersonation_controller.js +3 -8
- package/build/src/host/admin_console/console_overview_controller.d.ts +2 -2
- package/build/src/host/admin_console/console_overview_controller.js +4 -2
- package/build/src/host/admin_validators.d.ts +15 -11
- package/build/src/host/admin_validators.js +16 -9
- package/build/src/host/client_names.d.ts +9 -2
- package/build/src/host/client_names.js +11 -3
- package/build/src/host/consent_params.d.ts +7 -0
- package/build/src/host/consent_params.js +27 -0
- package/build/src/host/controllers/account_apps_controller.js +4 -3
- package/build/src/host/controllers/account_confirm_controller.d.ts +0 -7
- package/build/src/host/controllers/account_confirm_controller.js +0 -6
- package/build/src/host/controllers/account_orgs_controller.d.ts +0 -50
- package/build/src/host/controllers/account_orgs_controller.js +7 -113
- package/build/src/host/controllers/account_security_controller.js +7 -11
- package/build/src/host/controllers/agent_consent_controller.js +23 -5
- package/build/src/host/controllers/interaction_controller.js +6 -2
- package/build/src/host/controllers/poppy_consent_controller.js +33 -7
- package/build/src/host/controllers/registration_controller.js +7 -2
- package/build/src/host/i18n.d.ts +0 -4
- package/build/src/host/i18n.js +0 -6
- package/build/src/host/impersonation.d.ts +7 -15
- package/build/src/host/impersonation.js +7 -16
- package/build/src/host/interaction_recovery.js +4 -1
- package/build/src/host/login_attempt.js +5 -1
- package/build/src/host/middleware/account_auth.js +6 -0
- package/build/src/host/rate_limit.js +5 -1
- package/build/src/host/register_auth_host.d.ts +6 -0
- package/build/src/host/register_auth_host.js +32 -8
- package/build/src/host/renderers/inertia_renderer.d.ts +9 -21
- package/build/src/host/renderers/inertia_renderer.js +5 -6
- package/build/src/host/screen_props.d.ts +309 -0
- package/build/src/host/screen_props.js +11 -0
- package/build/src/host/sudo_mode.d.ts +5 -11
- package/build/src/host/sudo_mode.js +5 -11
- package/build/src/host/ui-dist/assets/index-D0EIeyKK.js +67 -0
- package/build/src/host/ui-dist/index.html +1 -1
- package/build/src/password/password_manager.js +5 -0
- package/build/src/provider/build_provider.js +0 -34
- package/build/src/provider/oidc_service.d.ts +0 -2
- package/build/src/provider/oidc_service.js +1 -12
- package/build/src/provider/token_exchange.d.ts +1 -3
- package/build/src/provider/token_exchange.js +1 -1
- package/build/stubs/config/authkit_react.stub +3 -3
- package/package.json +9 -8
- package/skills/authkit-interactions/SKILL.md +32 -5
- package/stubs/config/authkit_react.stub +3 -3
- package/build/src/commands/import_clients.d.ts +0 -40
- package/build/src/commands/import_clients.js +0 -81
- package/build/src/host/ui-dist/assets/index-DUVBDAyh.js +0 -67
- package/build/src/mixins/with_mfa.d.ts +0 -35
- package/build/src/mixins/with_mfa.js +0 -21
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
9
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
10
|
<link href="https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700&family=JetBrains+Mono:ital,wght@0,400;0,500;0,600;1,400&display=swap" rel="stylesheet">
|
|
11
|
-
<script type="module" crossorigin src="/__AUTHKIT_BASE__/assets/index-
|
|
11
|
+
<script type="module" crossorigin src="/__AUTHKIT_BASE__/assets/index-D0EIeyKK.js"></script>
|
|
12
12
|
<link rel="stylesheet" crossorigin href="/__AUTHKIT_BASE__/assets/index-DTSmD4RU.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
@@ -140,6 +140,11 @@ export class PasswordManager {
|
|
|
140
140
|
* mantendo este manager desacoplado do Lucid.
|
|
141
141
|
*/
|
|
142
142
|
async verify(hashedPassword, plainPassword, hooks) {
|
|
143
|
+
// No password at all (a form posted with only the e-mail): a failed check,
|
|
144
|
+
// not a TypeError from the HMAC/hasher.
|
|
145
|
+
if (typeof plainPassword !== 'string' || plainPassword === '') {
|
|
146
|
+
return { ok: false, rehash: false };
|
|
147
|
+
}
|
|
143
148
|
const peppers = resolvePeppers(this.pepper);
|
|
144
149
|
const currentPepper = Array.isArray(this.pepper) ? this.pepper[0] : (this.pepper ?? '');
|
|
145
150
|
for (let i = 0; i < peppers.length; i++) {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as oidc from 'oidc-provider';
|
|
2
2
|
import { pickModelAdapterClass } from '../adapters/factory.js';
|
|
3
3
|
import { normalizeActiveOrg, readActiveOrgFromKoaCtx } from '../host/active_org_cookie.js';
|
|
4
|
-
import { assertClientMetadata } from '../host/client_metadata.js';
|
|
5
4
|
import { findMcpResource, mcpAuthorizeMiddleware } from '../mcp/mcp_oauth.js';
|
|
6
5
|
import { createDeviceSources } from './device_sources.js';
|
|
7
6
|
import { createLogoutSources } from './logout_sources.js';
|
|
@@ -140,39 +139,6 @@ export function buildProvider(config, options, sessionTtlHolder, tokenTtlHolder)
|
|
|
140
139
|
// `pickModelAdapterClass`, usada também pelos serviços que instanciam
|
|
141
140
|
// adapter na mão. Sem `session.adapter`, as duas classes são a mesma.
|
|
142
141
|
adapter: ((name) => new (pickModelAdapterClass(name, config.AdapterClass, config.SessionAdapterClass))(name)),
|
|
143
|
-
clients: config.clients.map((c) => {
|
|
144
|
-
const grants = c.grants ?? ['authorization_code', 'refresh_token'];
|
|
145
|
-
const tokenEndpointAuthMethod = c.tokenEndpointAuthMethod ?? (c.clientSecret ? 'client_secret_basic' : 'none');
|
|
146
|
-
// Clients do `defineConfig` passam pela MESMA validação do admin/CLI/import:
|
|
147
|
-
// um nativo com secret (ou redirect inválido pro tipo) falha no boot, não em runtime.
|
|
148
|
-
assertClientMetadata({
|
|
149
|
-
applicationType: c.applicationType ?? 'web',
|
|
150
|
-
tokenEndpointAuthMethod,
|
|
151
|
-
grantTypes: grants,
|
|
152
|
-
redirectUris: c.redirectUris,
|
|
153
|
-
postLogoutRedirectUris: c.postLogoutRedirectUris ?? [],
|
|
154
|
-
});
|
|
155
|
-
return {
|
|
156
|
-
client_id: c.clientId,
|
|
157
|
-
client_secret: c.clientSecret,
|
|
158
|
-
redirect_uris: c.redirectUris,
|
|
159
|
-
post_logout_redirect_uris: c.postLogoutRedirectUris ?? [],
|
|
160
|
-
grant_types: grants,
|
|
161
|
-
response_types: (c.grants ?? ['authorization_code']).includes('authorization_code')
|
|
162
|
-
? ['code']
|
|
163
|
-
: [],
|
|
164
|
-
token_endpoint_auth_method: tokenEndpointAuthMethod,
|
|
165
|
-
// App nativo (RFC 8252): libera redirect de esquema privado/loopback. Sem a
|
|
166
|
-
// chave, o oidc-provider assume `web` — o comportamento de sempre.
|
|
167
|
-
...(c.applicationType === 'native' ? { application_type: 'native' } : {}),
|
|
168
|
-
// OIDC Back-Channel Logout: só envia as chaves quando o client as declara,
|
|
169
|
-
// p/ não forçar metadata vazio em clients que não usam o recurso.
|
|
170
|
-
...(c.backchannelLogoutUri ? { backchannel_logout_uri: c.backchannelLogoutUri } : {}),
|
|
171
|
-
...(c.backchannelLogoutSessionRequired !== undefined
|
|
172
|
-
? { backchannel_logout_session_required: c.backchannelLogoutSessionRequired }
|
|
173
|
-
: {}),
|
|
174
|
-
};
|
|
175
|
-
}),
|
|
176
142
|
findAccount: options.findAccount,
|
|
177
143
|
// Grant REAPROVEITADO (consent já lembrado): o org capturado no primeiro
|
|
178
144
|
// consent ficaria VELHO se o usuário trocasse de org no console e voltasse a
|
|
@@ -104,6 +104,4 @@ export declare class OidcService {
|
|
|
104
104
|
* é um detalhe interno acessível via o helper `weak_cache` do próprio provider.
|
|
105
105
|
*/
|
|
106
106
|
evictDynamicClientCache(): Promise<void>;
|
|
107
|
-
/** Verifica client_id + client_secret contra os clients da config (p/ endpoints custom como introspecção de PAT). */
|
|
108
|
-
verifyClientCredentials(clientId: string, clientSecret: string): boolean;
|
|
109
107
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createHmac
|
|
1
|
+
import { createHmac } from 'node:crypto';
|
|
2
2
|
import { NoopRecorder } from '@adonis-agora/authkit-core';
|
|
3
3
|
import Koa from 'koa';
|
|
4
4
|
import mount from 'koa-mount';
|
|
@@ -56,7 +56,6 @@ export class OidcService {
|
|
|
56
56
|
sessionTtlHolder;
|
|
57
57
|
/** Holder mutável dos TTLs de access/id/refresh tokens (lido sincronamente pelo oidc-provider). */
|
|
58
58
|
tokenTtlHolder;
|
|
59
|
-
#clients;
|
|
60
59
|
#config;
|
|
61
60
|
/** Closures de acesso ao cofre de chaves (injetadas pelo provider para hot-reload). */
|
|
62
61
|
#deps;
|
|
@@ -87,7 +86,6 @@ export class OidcService {
|
|
|
87
86
|
}
|
|
88
87
|
constructor(config, appKey, recorder = new NoopRecorder(), deps = {}) {
|
|
89
88
|
this.#config = config;
|
|
90
|
-
this.#clients = config.clients ?? [];
|
|
91
89
|
this.#appKey = appKey;
|
|
92
90
|
this.recorder = recorder;
|
|
93
91
|
this.#deps = deps;
|
|
@@ -340,13 +338,4 @@ export class OidcService {
|
|
|
340
338
|
// imediata da entrada órfã. Best-effort — não propaga erro pro caminho da request.
|
|
341
339
|
}
|
|
342
340
|
}
|
|
343
|
-
/** Verifica client_id + client_secret contra os clients da config (p/ endpoints custom como introspecção de PAT). */
|
|
344
|
-
verifyClientCredentials(clientId, clientSecret) {
|
|
345
|
-
const client = this.#clients.find((c) => c.clientId === clientId);
|
|
346
|
-
if (!client?.clientSecret)
|
|
347
|
-
return false;
|
|
348
|
-
const a = Buffer.from(client.clientSecret);
|
|
349
|
-
const b = Buffer.from(clientSecret);
|
|
350
|
-
return a.length === b.length && timingSafeEqual(a, b);
|
|
351
|
-
}
|
|
352
341
|
}
|
|
@@ -32,11 +32,9 @@ export interface TokenExchangeDeps {
|
|
|
32
32
|
* Allowlist de roles que autorizam a impersonation. Default: `['ADMIN']`.
|
|
33
33
|
* Aceita o papel EXATO (ex.: `'admin'` minúsculo) do app — resolve contra a
|
|
34
34
|
* MESMA fonte de roles do `resolveTokenRoles`/`globalRoles`, não a um papel
|
|
35
|
-
* hardcoded.
|
|
35
|
+
* hardcoded.
|
|
36
36
|
*/
|
|
37
37
|
adminRoles?: string[];
|
|
38
|
-
/** @deprecated Use `adminRoles` (allowlist). Mantido como alias singular. */
|
|
39
|
-
adminRole?: string;
|
|
40
38
|
/**
|
|
41
39
|
* Resource indicators (RFC 8707) suportados pelo provider. Quando o pedido traz
|
|
42
40
|
* `audience`/`resource`, validamos contra esta lista; um alvo não suportado é
|
|
@@ -36,7 +36,7 @@ function intersectScopes(requested, allowed) {
|
|
|
36
36
|
return out.join(' ');
|
|
37
37
|
}
|
|
38
38
|
export function registerTokenExchange(provider, deps) {
|
|
39
|
-
const adminRoles = deps.adminRoles ??
|
|
39
|
+
const adminRoles = deps.adminRoles ?? ['ADMIN'];
|
|
40
40
|
const supportedResources = new Set(deps.supportedResources ?? []);
|
|
41
41
|
const handler = async (ctx) => {
|
|
42
42
|
const { params, client } = ctx.oidc;
|
|
@@ -9,12 +9,12 @@ const authServerConfig = defineConfig({
|
|
|
9
9
|
issuer: env.get('AUTHKIT_ISSUER'),
|
|
10
10
|
adapter: adapters.redis({ connection: 'main' }),
|
|
11
11
|
jwks: { source: 'managed', algorithm: 'RS256' },
|
|
12
|
-
clients: [
|
|
13
|
-
// registre seus clients OIDC aqui
|
|
14
|
-
],
|
|
15
12
|
ttl: { accessToken: '15m', refreshToken: '30d' },
|
|
16
13
|
globalRolesClaim: 'roles',
|
|
17
14
|
accountStore: lucidAccountStore(AuthUser),
|
|
15
|
+
// Clients são gerenciados em runtime via console admin (/admin/clients) ou
|
|
16
|
+
// Admin API, sem necessidade de redeploy.
|
|
17
|
+
// Use: node ace authkit:clients:create --redirect-uri=https://app/cb
|
|
18
18
|
/**
|
|
19
19
|
* Renderer Inertia/React: apenas as views listadas em "views" vão ao Inertia;
|
|
20
20
|
* as demais (incluindo todas as telas admin/*) recaem silenciosamente no
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adonis-agora/authkit-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-rc.0",
|
|
4
4
|
"description": "AdonisJS OIDC/OAuth2 provider (Identity Provider) toolkit: ejectable auth server with sessions, rate-limiting, MFA/TOTP, audit log, federated logout and OpenTelemetry metrics.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "dudousxd",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/DavideCarvalho/adonis-agora-authkit.git",
|
|
9
|
+
"url": "git+https://github.com/DavideCarvalho/adonis-agora-authkit.git",
|
|
10
10
|
"directory": "packages/authkit-server"
|
|
11
11
|
},
|
|
12
12
|
"homepage": "https://davidecarvalho.github.io/agora/docs/authkit",
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
"files": [
|
|
36
36
|
"build",
|
|
37
37
|
"stubs",
|
|
38
|
-
"skills"
|
|
38
|
+
"skills",
|
|
39
|
+
"CHANGELOG.md"
|
|
39
40
|
],
|
|
40
41
|
"exports": {
|
|
41
42
|
".": "./build/index.js",
|
|
@@ -60,9 +61,9 @@
|
|
|
60
61
|
"node": ">=24"
|
|
61
62
|
},
|
|
62
63
|
"peerDependencies": {
|
|
63
|
-
"@adonis-agora/durable": ">=0.7.0 <1.0.0",
|
|
64
|
-
"@adonis-agora/media": ">=0.5.0 <1.0.0",
|
|
65
|
-
"@adonis-agora/telescope": ">=0.1.0 <1.0.0",
|
|
64
|
+
"@adonis-agora/durable": ">=0.7.0 <2.0.0 || >=1.0.0-rc.0 <2.0.0",
|
|
65
|
+
"@adonis-agora/media": ">=0.5.0 <2.0.0 || >=1.0.0-rc.0 <2.0.0",
|
|
66
|
+
"@adonis-agora/telescope": ">=0.1.0 <2.0.0 || >=1.0.0-rc.0 <2.0.0",
|
|
66
67
|
"@adonisjs/ally": "^6.3.0",
|
|
67
68
|
"@adonisjs/auth": "^10.1.0",
|
|
68
69
|
"@adonisjs/core": "^7.5.0",
|
|
@@ -105,7 +106,7 @@
|
|
|
105
106
|
}
|
|
106
107
|
},
|
|
107
108
|
"dependencies": {
|
|
108
|
-
"@adonis-agora/authkit-core": "0.
|
|
109
|
+
"@adonis-agora/authkit-core": "1.0.0-rc.0",
|
|
109
110
|
"@simplewebauthn/browser": "14.0.0",
|
|
110
111
|
"@simplewebauthn/server": "14.1.0",
|
|
111
112
|
"jose": "6.2.15",
|
|
@@ -116,7 +117,7 @@
|
|
|
116
117
|
"qrcode": "1.5.4"
|
|
117
118
|
},
|
|
118
119
|
"devDependencies": {
|
|
119
|
-
"@adonis-agora/authkit-react": "0.
|
|
120
|
+
"@adonis-agora/authkit-react": "1.0.0-rc.0",
|
|
120
121
|
"@adonis-agora/durable": "0.59.0",
|
|
121
122
|
"@adonis-agora/telescope": "0.27.1",
|
|
122
123
|
"@adonisjs/ally": "6.3.2",
|
|
@@ -128,15 +128,42 @@ defineConfig({
|
|
|
128
128
|
issuer: env.get('AUTHKIT_ISSUER'),
|
|
129
129
|
adapter: adapters.database(),
|
|
130
130
|
accountStore: lucidAccountStore(AuthUser),
|
|
131
|
-
|
|
131
|
+
// `views` is required: only listed screens render as Inertia pages.
|
|
132
|
+
// Any other screen falls back to the built-in Edge view.
|
|
133
|
+
render: inertiaRenderer({ prefix: 'authkit', views: ['login', 'consent'] }),
|
|
132
134
|
})
|
|
133
135
|
```
|
|
134
136
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
+
Every screen (login, signup, consent, MFA, `session-expired`, `throttled`,
|
|
138
|
+
`agents/*`, `account/*`) renders through `render`, so any of them can be a host
|
|
139
|
+
page. Type each page with `AuthkitScreenProps['<view>']` (it includes `messages`);
|
|
140
|
+
the controllers are checked against the same types:
|
|
137
141
|
|
|
138
|
-
|
|
139
|
-
|
|
142
|
+
```tsx
|
|
143
|
+
import type { AuthkitScreenProps } from '@adonis-agora/authkit-server'
|
|
144
|
+
|
|
145
|
+
export default function SessionExpired({
|
|
146
|
+
loginUrl,
|
|
147
|
+
messages,
|
|
148
|
+
}: AuthkitScreenProps['session-expired']) {
|
|
149
|
+
return <a href={loginUrl}>{messages['session_expired.login_link']}</a>
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
- The page file must be `inertia/pages/<prefix>/<view>.tsx` with the view's
|
|
154
|
+
kebab-case name: Inertia resolves it by string, so a renamed file only fails at
|
|
155
|
+
runtime.
|
|
156
|
+
- A screen of your own (a `render` wrapper that swaps `login` for an audience
|
|
157
|
+
variant, say) must be declared by augmenting `AuthkitScreenProps` in
|
|
158
|
+
`declare module '@adonis-agora/authkit-server'`; otherwise `views` rejects it.
|
|
159
|
+
- Set `interactionRecovery: { redirectTo: '/auth/login' }` so `session-expired`
|
|
160
|
+
restarts the sign-in, not the account console's `/account/login`.
|
|
161
|
+
- The admin console (`/admin`) is not themed: build a custom admin UI with the
|
|
162
|
+
admin query options of `@adonis-agora/authkit-react` instead. Account surfaces
|
|
163
|
+
inside the app use the React components or the account query options.
|
|
164
|
+
|
|
165
|
+
Source: `src/host/screen_props.ts` (`AuthkitScreenProps`), `index.ts` exports
|
|
166
|
+
(`inertiaRenderer`, `edgeRenderer`); docs page "Themed Screens".
|
|
140
167
|
|
|
141
168
|
### Pattern 4 — test flows without booting an IdP
|
|
142
169
|
|
|
@@ -9,12 +9,12 @@ const authServerConfig = defineConfig({
|
|
|
9
9
|
issuer: env.get('AUTHKIT_ISSUER'),
|
|
10
10
|
adapter: adapters.redis({ connection: 'main' }),
|
|
11
11
|
jwks: { source: 'managed', algorithm: 'RS256' },
|
|
12
|
-
clients: [
|
|
13
|
-
// registre seus clients OIDC aqui
|
|
14
|
-
],
|
|
15
12
|
ttl: { accessToken: '15m', refreshToken: '30d' },
|
|
16
13
|
globalRolesClaim: 'roles',
|
|
17
14
|
accountStore: lucidAccountStore(AuthUser),
|
|
15
|
+
// Clients são gerenciados em runtime via console admin (/admin/clients) ou
|
|
16
|
+
// Admin API, sem necessidade de redeploy.
|
|
17
|
+
// Use: node ace authkit:clients:create --redirect-uri=https://app/cb
|
|
18
18
|
/**
|
|
19
19
|
* Renderer Inertia/React: apenas as views listadas em "views" vão ao Inertia;
|
|
20
20
|
* as demais (incluindo todas as telas admin/*) recaem silenciosamente no
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import type { ClientConfig } from '@adonis-agora/authkit-core';
|
|
2
|
-
import type { AdminClientsService } from '../host/admin_clients_service.js';
|
|
3
|
-
/** Resultado da importação de um client individual. */
|
|
4
|
-
export type ClientImportOutcome = 'created' | 'skipped' | 'error';
|
|
5
|
-
/** Linha de relatório por client. */
|
|
6
|
-
export interface ClientImportEntry {
|
|
7
|
-
clientId: string;
|
|
8
|
-
outcome: ClientImportOutcome;
|
|
9
|
-
/** Razão do erro ou 'already exists' no caso de skip. */
|
|
10
|
-
reason?: string;
|
|
11
|
-
/**
|
|
12
|
-
* Secret gerado (em claro, mostrado UMA vez) — presente apenas em `created` e
|
|
13
|
-
* somente quando o client é confidencial. Não recuperável depois.
|
|
14
|
-
*/
|
|
15
|
-
clientSecret?: string;
|
|
16
|
-
}
|
|
17
|
-
/** Relatório agregado da operação. */
|
|
18
|
-
export interface ClientsImportReport {
|
|
19
|
-
created: number;
|
|
20
|
-
skipped: number;
|
|
21
|
-
errors: number;
|
|
22
|
-
entries: ClientImportEntry[];
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Importa a lista de clients estáticos do config para o adapter/DB via
|
|
26
|
-
* {@link AdminClientsService}. Idempotente: clients cujo `client_id` já existe no
|
|
27
|
-
* adapter são pulados (preservando o secret original). O `client_secret` do config
|
|
28
|
-
* estático é PRESERVADO na criação (passado diretamente como `clientId` ficará com
|
|
29
|
-
* o secret original). Para clients sem secret (public), `clientSecret` fica omitido.
|
|
30
|
-
*
|
|
31
|
-
* A função é PURA quanto a I/O do config (recebe os records já parseados) —
|
|
32
|
-
* testável em isolamento. O único I/O é via `svc` (adapter).
|
|
33
|
-
*
|
|
34
|
-
* @param clients - Lista de {@link ClientConfig} vindos de `config.clients`.
|
|
35
|
-
* @param svc - Serviço admin de clients (CRUD sobre o adapter).
|
|
36
|
-
* @param options - `dryRun`: não persiste, só relata o que SERIA feito.
|
|
37
|
-
*/
|
|
38
|
-
export declare function importClients(clients: ClientConfig[], svc: AdminClientsService, options?: {
|
|
39
|
-
dryRun?: boolean;
|
|
40
|
-
}): Promise<ClientsImportReport>;
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Importa a lista de clients estáticos do config para o adapter/DB via
|
|
3
|
-
* {@link AdminClientsService}. Idempotente: clients cujo `client_id` já existe no
|
|
4
|
-
* adapter são pulados (preservando o secret original). O `client_secret` do config
|
|
5
|
-
* estático é PRESERVADO na criação (passado diretamente como `clientId` ficará com
|
|
6
|
-
* o secret original). Para clients sem secret (public), `clientSecret` fica omitido.
|
|
7
|
-
*
|
|
8
|
-
* A função é PURA quanto a I/O do config (recebe os records já parseados) —
|
|
9
|
-
* testável em isolamento. O único I/O é via `svc` (adapter).
|
|
10
|
-
*
|
|
11
|
-
* @param clients - Lista de {@link ClientConfig} vindos de `config.clients`.
|
|
12
|
-
* @param svc - Serviço admin de clients (CRUD sobre o adapter).
|
|
13
|
-
* @param options - `dryRun`: não persiste, só relata o que SERIA feito.
|
|
14
|
-
*/
|
|
15
|
-
export async function importClients(clients, svc, options = {}) {
|
|
16
|
-
const report = { created: 0, skipped: 0, errors: 0, entries: [] };
|
|
17
|
-
for (const client of clients) {
|
|
18
|
-
const clientId = client.clientId;
|
|
19
|
-
// Verifica se já existe no adapter.
|
|
20
|
-
let exists = false;
|
|
21
|
-
try {
|
|
22
|
-
const found = await svc.find(clientId);
|
|
23
|
-
exists = !!found;
|
|
24
|
-
}
|
|
25
|
-
catch (err) {
|
|
26
|
-
report.errors++;
|
|
27
|
-
report.entries.push({
|
|
28
|
-
clientId,
|
|
29
|
-
outcome: 'error',
|
|
30
|
-
reason: `falha ao verificar existência: ${err.message}`,
|
|
31
|
-
});
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
if (exists) {
|
|
35
|
-
report.skipped++;
|
|
36
|
-
report.entries.push({ clientId, outcome: 'skipped', reason: 'already exists' });
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
if (options.dryRun) {
|
|
40
|
-
// Dry-run: conta como "would create" sem persistir.
|
|
41
|
-
report.created++;
|
|
42
|
-
report.entries.push({ clientId, outcome: 'created' });
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
// Determina o auth method a partir do config estático.
|
|
46
|
-
const authMethod = client.tokenEndpointAuthMethod ?? (client.clientSecret ? 'client_secret_basic' : 'none');
|
|
47
|
-
const applicationType = client.applicationType ?? 'web';
|
|
48
|
-
try {
|
|
49
|
-
// Cria via AdminClientsService, que usa o mesmo caminho do console admin / registro dinâmico.
|
|
50
|
-
// Para preservar o client_secret do config estático, escrevemos diretamente via adapter —
|
|
51
|
-
// o `svc.create` gera um secret aleatório. Usamos a API interna do svc para construir o
|
|
52
|
-
// payload com o secret original e persistir.
|
|
53
|
-
const preservedSecret = client.clientSecret;
|
|
54
|
-
const created = await svc.createWithSecret({
|
|
55
|
-
clientId,
|
|
56
|
-
applicationType,
|
|
57
|
-
redirectUris: client.redirectUris ?? [],
|
|
58
|
-
postLogoutRedirectUris: client.postLogoutRedirectUris ?? [],
|
|
59
|
-
grantTypes: client.grants ?? ['authorization_code', 'refresh_token'],
|
|
60
|
-
tokenEndpointAuthMethod: authMethod,
|
|
61
|
-
backchannelLogoutUri: client.backchannelLogoutUri,
|
|
62
|
-
backchannelLogoutSessionRequired: client.backchannelLogoutSessionRequired,
|
|
63
|
-
}, preservedSecret);
|
|
64
|
-
report.created++;
|
|
65
|
-
report.entries.push({
|
|
66
|
-
clientId,
|
|
67
|
-
outcome: 'created',
|
|
68
|
-
clientSecret: created.clientSecret,
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
catch (err) {
|
|
72
|
-
report.errors++;
|
|
73
|
-
report.entries.push({
|
|
74
|
-
clientId,
|
|
75
|
-
outcome: 'error',
|
|
76
|
-
reason: err.message,
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return report;
|
|
81
|
-
}
|