@adonis-agora/authkit-server 0.90.2 → 1.0.0-rc.1

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 (99) hide show
  1. package/CHANGELOG.md +3654 -0
  2. package/build/commands/clients_create.d.ts +1 -0
  3. package/build/commands/clients_create.js +15 -2
  4. package/build/commands/commands.json +7 -0
  5. package/build/host/ui/admin.html +1 -1
  6. package/build/host/views/account/apps.edge +8 -3
  7. package/build/host/views/partials/styles.edge +1 -1
  8. package/build/index.d.ts +5 -3
  9. package/build/index.js +4 -5
  10. package/build/providers/authkit_server_provider.js +8 -0
  11. package/build/src/accounts/lucid_account_store.d.ts +0 -18
  12. package/build/src/accounts/lucid_account_store.js +7 -48
  13. package/build/src/accounts/lucid_store/mfa.js +1 -1
  14. package/build/src/accounts/lucid_store/organizations.d.ts +1 -1
  15. package/build/src/accounts/lucid_store/password_hygiene.d.ts +2 -3
  16. package/build/src/accounts/lucid_store/password_hygiene.js +2 -3
  17. package/build/src/accounts/lucid_store/shared.d.ts +0 -7
  18. package/build/src/accounts/lucid_store/shared.js +0 -14
  19. package/build/src/accounts/lucid_stores.d.ts +1 -1
  20. package/build/src/adapters/redis_adapter.js +12 -2
  21. package/build/src/console_authorize.d.ts +56 -0
  22. package/build/src/console_authorize.js +85 -0
  23. package/build/src/define_config.d.ts +26 -14
  24. package/build/src/define_config.js +5 -3
  25. package/build/src/doctor/checks.d.ts +10 -5
  26. package/build/src/doctor/checks.js +52 -8
  27. package/build/src/host/account_api/account_api_controller.d.ts +2 -1
  28. package/build/src/host/account_api/account_api_controller.js +5 -11
  29. package/build/src/host/admin_api/api_clients_controller.d.ts +9 -3
  30. package/build/src/host/admin_api/dto.d.ts +3 -1
  31. package/build/src/host/admin_api/dto.js +3 -1
  32. package/build/src/host/admin_clients_service.d.ts +13 -5
  33. package/build/src/host/admin_clients_service.js +28 -6
  34. package/build/src/host/admin_console/admin_shell_controller.d.ts +16 -1
  35. package/build/src/host/admin_console/admin_shell_controller.js +21 -7
  36. package/build/src/host/admin_console/console_clients_controller.d.ts +21 -2
  37. package/build/src/host/admin_console/console_clients_controller.js +9 -0
  38. package/build/src/host/admin_console/console_impersonation_controller.js +3 -8
  39. package/build/src/host/admin_console/console_overview_controller.d.ts +2 -2
  40. package/build/src/host/admin_console/console_overview_controller.js +4 -2
  41. package/build/src/host/admin_console_warning.d.ts +8 -0
  42. package/build/src/host/admin_console_warning.js +18 -0
  43. package/build/src/host/admin_validators.d.ts +15 -11
  44. package/build/src/host/admin_validators.js +16 -9
  45. package/build/src/host/auth_host_config.d.ts +5 -0
  46. package/build/src/host/client_names.d.ts +9 -2
  47. package/build/src/host/client_names.js +11 -3
  48. package/build/src/host/consent_params.d.ts +7 -0
  49. package/build/src/host/consent_params.js +27 -0
  50. package/build/src/host/console_denied_page.d.ts +6 -0
  51. package/build/src/host/console_denied_page.js +18 -0
  52. package/build/src/host/console_sso.d.ts +24 -0
  53. package/build/src/host/console_sso.js +70 -0
  54. package/build/src/host/controllers/account_apps_controller.js +4 -3
  55. package/build/src/host/controllers/account_confirm_controller.d.ts +0 -7
  56. package/build/src/host/controllers/account_confirm_controller.js +0 -6
  57. package/build/src/host/controllers/account_orgs_controller.d.ts +0 -50
  58. package/build/src/host/controllers/account_orgs_controller.js +7 -113
  59. package/build/src/host/controllers/account_security_controller.js +7 -11
  60. package/build/src/host/controllers/account_session_controller.js +10 -1
  61. package/build/src/host/controllers/agent_consent_controller.js +23 -5
  62. package/build/src/host/controllers/console_sso_controller.d.ts +12 -0
  63. package/build/src/host/controllers/console_sso_controller.js +61 -0
  64. package/build/src/host/controllers/interaction_controller.js +9 -3
  65. package/build/src/host/controllers/poppy_consent_controller.js +33 -7
  66. package/build/src/host/controllers/registration_controller.js +7 -2
  67. package/build/src/host/i18n.d.ts +0 -4
  68. package/build/src/host/i18n.js +0 -6
  69. package/build/src/host/impersonation.d.ts +7 -15
  70. package/build/src/host/impersonation.js +7 -16
  71. package/build/src/host/interaction_recovery.js +4 -1
  72. package/build/src/host/login_attempt.js +5 -1
  73. package/build/src/host/middleware/account_auth.js +6 -0
  74. package/build/src/host/rate_limit.js +5 -1
  75. package/build/src/host/register_auth_host.d.ts +8 -0
  76. package/build/src/host/register_auth_host.js +116 -11
  77. package/build/src/host/renderers/inertia_renderer.d.ts +9 -21
  78. package/build/src/host/renderers/inertia_renderer.js +5 -6
  79. package/build/src/host/screen_props.d.ts +309 -0
  80. package/build/src/host/screen_props.js +11 -0
  81. package/build/src/host/sudo_mode.d.ts +5 -11
  82. package/build/src/host/sudo_mode.js +5 -11
  83. package/build/src/host/ui-dist/assets/index-D0EIeyKK.js +67 -0
  84. package/build/src/host/ui-dist/index.html +1 -1
  85. package/build/src/password/password_manager.js +5 -0
  86. package/build/src/provider/build_provider.js +6 -34
  87. package/build/src/provider/oidc_service.d.ts +0 -2
  88. package/build/src/provider/oidc_service.js +1 -12
  89. package/build/src/provider/token_exchange.d.ts +1 -3
  90. package/build/src/provider/token_exchange.js +1 -1
  91. package/build/stubs/config/authkit_react.stub +3 -3
  92. package/package.json +12 -11
  93. package/skills/authkit-interactions/SKILL.md +32 -5
  94. package/stubs/config/authkit_react.stub +3 -3
  95. package/build/src/commands/import_clients.d.ts +0 -40
  96. package/build/src/commands/import_clients.js +0 -81
  97. package/build/src/host/ui-dist/assets/index-DUVBDAyh.js +0 -67
  98. package/build/src/mixins/with_mfa.d.ts +0 -35
  99. 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-DUVBDAyh.js"></script>
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,7 @@
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';
4
+ import { consoleSsoClientMetadata } from '../host/console_sso.js';
5
5
  import { findMcpResource, mcpAuthorizeMiddleware } from '../mcp/mcp_oauth.js';
6
6
  import { createDeviceSources } from './device_sources.js';
7
7
  import { createLogoutSources } from './logout_sources.js';
@@ -130,6 +130,11 @@ export function buildProvider(config, options, sessionTtlHolder, tokenTtlHolder)
130
130
  }
131
131
  : {};
132
132
  const provider = new oidc.Provider(config.issuer, {
133
+ // Client interno do login do console pelo IdP (ver `host/console_sso.ts`).
134
+ // Estático: não passa pelo adapter, então não aparece no console de clients.
135
+ ...(config.accountSession?.acceptIdpSession
136
+ ? { clients: [consoleSsoClientMetadata(config.issuer)] }
137
+ : {}),
133
138
  // O ator (`act`, RFC 8693) dos access tokens de impersonation — ver
134
139
  // `impersonationExtraClaims` em `token_exchange.ts`. Sem isto o token trocado
135
140
  // não se distingue de um token do próprio alvo no resource server.
@@ -140,39 +145,6 @@ export function buildProvider(config, options, sessionTtlHolder, tokenTtlHolder)
140
145
  // `pickModelAdapterClass`, usada também pelos serviços que instanciam
141
146
  // adapter na mão. Sem `session.adapter`, as duas classes são a mesma.
142
147
  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
148
  findAccount: options.findAccount,
177
149
  // Grant REAPROVEITADO (consent já lembrado): o org capturado no primeiro
178
150
  // 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, timingSafeEqual } from 'node:crypto';
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. Mantém `adminRole` como alias singular para back-compat.
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 ?? (deps.adminRole ? [deps.adminRole] : ['ADMIN']);
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.90.2",
3
+ "version": "1.0.0-rc.1",
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,18 +106,18 @@
105
106
  }
106
107
  },
107
108
  "dependencies": {
108
- "@adonis-agora/authkit-core": "0.10.1",
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",
112
- "koa": "3.2.1",
113
+ "koa": "3.3.0",
113
114
  "koa-mount": "4.2.0",
114
115
  "oidc-provider": "9.13.3",
115
- "otplib": "13.5.0",
116
+ "otplib": "13.6.0",
116
117
  "qrcode": "1.5.4"
117
118
  },
118
119
  "devDependencies": {
119
- "@adonis-agora/authkit-react": "0.26.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",
@@ -139,7 +140,7 @@
139
140
  "@types/koa": "3.0.3",
140
141
  "@types/koa-mount": "4.0.5",
141
142
  "@types/luxon": "3.7.6",
142
- "@types/node": "26.6.5",
143
+ "@types/node": "26.6.6",
143
144
  "@types/qrcode": "1.5.6",
144
145
  "@types/react": "19.3.0",
145
146
  "@types/react-dom": "19.3.0",
@@ -128,15 +128,42 @@ defineConfig({
128
128
  issuer: env.get('AUTHKIT_ISSUER'),
129
129
  adapter: adapters.database(),
130
130
  accountStore: lucidAccountStore(AuthUser),
131
- render: inertiaRenderer(), // renders Inertia pages for /account/* and /auth/interaction/*
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
- Without any renderer, every `/account/*` and `/auth/interaction/*` request fails
136
- with an unexplained 500 (`render` is undefined).
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
- Source: `src/define_config.ts` (`render` JSDoc), `index.ts` exports
139
- (`inertiaRenderer`, `edgeRenderer`).
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
- }