@adonis-agora/authkit-server 1.0.0-rc.11 → 1.0.0-rc.13
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 +26 -0
- package/README.md +1 -1
- package/build/index.d.ts +2 -1
- package/build/index.js +1 -0
- package/build/providers/authkit_server_provider.js +15 -4
- package/build/src/accounts/lucid_account_store.js +4 -3
- package/build/src/accounts/lucid_store/mfa.js +1 -1
- package/build/src/accounts/lucid_store/organization_models.d.ts +9 -5
- package/build/src/accounts/lucid_store/organization_models.js +16 -12
- package/build/src/accounts/lucid_store/password_hygiene.js +4 -2
- package/build/src/accounts/lucid_store/shared.d.ts +6 -1
- package/build/src/accounts/lucid_store/shared.js +7 -6
- package/build/src/accounts/lucid_store/webauthn.js +1 -1
- package/build/src/adapters/database_adapter.d.ts +7 -1
- package/build/src/adapters/database_adapter.js +24 -13
- package/build/src/adapters/factory.d.ts +7 -0
- package/build/src/adapters/factory.js +6 -2
- package/build/src/agents/delegation_store.d.ts +5 -1
- package/build/src/agents/delegation_store.js +34 -21
- package/build/src/agents/extension.d.ts +16 -2
- package/build/src/agents/registry_store.d.ts +1 -0
- package/build/src/agents/registry_store.js +10 -8
- package/build/src/agents/runtime.js +6 -2
- package/build/src/commands/expire_scan_command.js +2 -3
- package/build/src/commands/settings_commands.js +4 -4
- package/build/src/define_config.d.ts +8 -0
- package/build/src/define_config.js +23 -6
- package/build/src/doctor/agora_checks.js +13 -0
- package/build/src/doctor/checks.d.ts +31 -1
- package/build/src/doctor/checks.js +118 -3
- package/build/src/doctor/db_schema_check.d.ts +6 -0
- package/build/src/doctor/db_schema_check.js +87 -0
- package/build/src/doctor/doctor_input.js +2 -0
- package/build/src/host/admin_sessions_service.js +6 -2
- package/build/src/host/csrf.d.ts +3 -0
- package/build/src/host/csrf.js +27 -7
- package/build/src/host/runtime_settings.d.ts +15 -0
- package/build/src/host/runtime_settings.js +27 -12
- package/build/src/keys/keystore_manager.js +2 -1
- package/build/src/keys/keystore_vault.d.ts +9 -1
- package/build/src/keys/keystore_vault.js +27 -9
- package/build/src/schema/ensure.d.ts +9 -0
- package/build/src/schema/ensure.js +46 -19
- package/build/src/schema/tables_location.d.ts +31 -2
- package/build/src/schema/tables_location.js +64 -0
- package/package.json +3 -3
- package/skills/authkit-idp-setup/SKILL.md +13 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @adonis-agora/authkit-server
|
|
2
2
|
|
|
3
|
+
## 1.0.0-rc.13
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#475](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/475) [`2ebb953`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/2ebb953d1d96eb128febc7cc0c3e6f55a6000cc2) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - `agora:doctor`'s `authkit.shield` check now reads the app's `config/shield.ts` and only warns when shield's CSRF check would really reject one of authkit's server-to-server POST endpoints (`{mountPath}/token`, `/token/introspection`, `/token/revocation`, `/authkit/pat/introspect`, and the personal-agent OAuth endpoints when enabled). It mirrors shield: CSRF off or a `methods` list without POST passes, an empty `methods` checks every method, and `exceptRoutes` may be a function (probed with a token-less POST) or a route-pattern array. `authkitCsrfExceptions` now matches the pathname anchored (`/oidc` or `/oidc/...`) instead of a substring, so `/admin/oidc/x`, `/oidc-clients` or `?next=/oidc` are no longer exempted.
|
|
8
|
+
|
|
9
|
+
## 1.0.0-rc.12
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [#474](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/474) [`df9e49d`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/df9e49d7817c03a7cce4cac1df709c7788d6307c) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - AuthKit's tables can live in their own database schema: `schema: { name: 'auth' }` in `defineConfig` puts every table AuthKit owns in `auth` (`"auth"."auth_settings"`) while the app stays in `public`. The schema is written into every statement, so it works on any connection and inside the app's own transaction, with no `searchPath` (which leaks across clients behind PgBouncer in transaction mode).
|
|
14
|
+
|
|
15
|
+
- The boot creates the schema (`CREATE SCHEMA IF NOT EXISTS`; MySQL: `CREATE DATABASE IF NOT EXISTS`) when `autoManage` is on, and fails with a message naming the schema and the grant when the role cannot create it. `ensureAuthkitSchema(db, { schema })` does the same from a migration.
|
|
16
|
+
- Covers the OIDC adapter table (`adapters.database({ schema })` overrides), the Lucid keystore (`jwks.store: { driver: 'lucid', schema }` overrides), the account store's tables (MFA, password history, organizations), runtime settings, session revocations, the personal-agent tables and agent extensions (`PersonalAgentHost.table(name)`, `AuthkitTable.create(table, { table })`, `ensureAuthkitTables(db, tables, { schema })`; Poppy uses them and `ensurePoppySchema(db, { schema })`).
|
|
17
|
+
- authkit-client: `lucidRevocationStore({ schema })`.
|
|
18
|
+
- SQLite with `schema.name` fails the boot instead of ignoring the option.
|
|
19
|
+
- `agora:doctor` gains `authkit.db-schema`: the schema exists, the tables are in it, the connection can write there. Silent without `schema.name`.
|
|
20
|
+
- The key-rotation lock key includes the schema, so two installs in two schemas of one database do not share it.
|
|
21
|
+
|
|
22
|
+
Without `schema.name` nothing changes.
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- Updated dependencies [[`df9e49d`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/df9e49d7817c03a7cce4cac1df709c7788d6307c)]:
|
|
27
|
+
- @adonis-agora/authkit-core@1.0.0-rc.2
|
|
28
|
+
|
|
3
29
|
## 1.0.0-rc.11
|
|
4
30
|
|
|
5
31
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -119,7 +119,7 @@ router.post('/auth/interaction/:uid/consent', [AuthInteractionController, 'conse
|
|
|
119
119
|
|
|
120
120
|
Escolha o backend no `config/authkit.ts`:
|
|
121
121
|
- `adapters.redis({ connection })` — requer `@adonisjs/redis` configurado.
|
|
122
|
-
- `adapters.database({ connection? })` — Lucid; tabela `authkit_oidc_payloads`, criada pelo `ensureAuthkitSchema` no boot (com `schema: { autoManage: false }`, chame-o numa migração). A coluna indexada `account_id` mantém as buscas e revogações por conta em SQL; linhas antigas com `account_id` NULL continuam sendo encontradas. Postgres, MySQL e SQLite.
|
|
122
|
+
- `adapters.database({ connection?, schema? })` — Lucid (default: `schema.connection`/`schema.name` do `defineConfig`); tabela `authkit_oidc_payloads`, criada pelo `ensureAuthkitSchema` no boot (com `schema: { autoManage: false }`, chame-o numa migração). A coluna indexada `account_id` mantém as buscas e revogações por conta em SQL; linhas antigas com `account_id` NULL continuam sendo encontradas. Postgres, MySQL e SQLite.
|
|
123
123
|
|
|
124
124
|
## Observabilidade
|
|
125
125
|
|
package/build/index.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export type { PersonalAgentIdentity } from './src/agents/agent_identity.js';
|
|
|
15
15
|
export { PersonalAgentVerifier } from './src/agents/agent_identity.js';
|
|
16
16
|
export type { PersonalAgentDelegationConfigInput, PersonalAgentRegistration, PersonalAgentResolver, PersonalAgentsConfigInput, ResolvedPersonalAgentsConfig, } from './src/agents/config.js';
|
|
17
17
|
export type { DelegationContext, DelegationGrantSummary, DelegationReceipt, } from './src/agents/delegation_service.js';
|
|
18
|
-
export type { AuthkitTable, PersonalAgentAccessTokens, PersonalAgentConsoleSummary, PersonalAgentExtension, PersonalAgentExtensionContext, PersonalAgentExtensionDefinition, PersonalAgentGrant, PersonalAgentGrantLedger, PersonalAgentGrantSummary, PersonalAgentHost, PersonalAgentProtocolRuntime, PersonalAgentRefreshToken, PersonalAgentRouteKit, ResolvedPersonalAgentExtension, } from './src/agents/extension.js';
|
|
18
|
+
export type { AuthkitTable, AuthkitTableContext, PersonalAgentAccessTokens, PersonalAgentConsoleSummary, PersonalAgentExtension, PersonalAgentExtensionContext, PersonalAgentExtensionDefinition, PersonalAgentGrant, PersonalAgentGrantLedger, PersonalAgentGrantSummary, PersonalAgentHost, PersonalAgentProtocolRuntime, PersonalAgentRefreshToken, PersonalAgentRouteKit, ResolvedPersonalAgentExtension, } from './src/agents/extension.js';
|
|
19
19
|
export type { PersonalAgentAuthOptions, PersonalAgentRequestContext, } from './src/agents/middleware.js';
|
|
20
20
|
export { personalAgentAuth, personalAgentOf, personalAgentReceipt, personalAgentSecurity, personalAgentStepUp, } from './src/agents/middleware.js';
|
|
21
21
|
export type { BuiltinProtocolId, PactOptions, PersonalAgentProtocol, } from './src/agents/protocol.js';
|
|
@@ -160,6 +160,7 @@ export { checkClientRegistration, classifyRedirect, type RedirectUriPolicy, type
|
|
|
160
160
|
export { registerOidcRoutes } from './src/register_routes.js';
|
|
161
161
|
export type { EnsureSchemaOptions, EnsureSchemaReport } from './src/schema/ensure.js';
|
|
162
162
|
export { ensureAuthkitSchema, ensureAuthkitTables } from './src/schema/ensure.js';
|
|
163
|
+
export { AuthkitSchemaError } from './src/schema/tables_location.js';
|
|
163
164
|
export type { ServiceTenantInfo } from './src/service_tenants/client_metadata.js';
|
|
164
165
|
export type { ResolvedServiceTenantResource, ResolvedServiceTenantsConfig, ServiceTenantResourceInput, ServiceTenantsConfigInput, } from './src/service_tenants/config.js';
|
|
165
166
|
export { AuthkitTenantVerifier, type AuthkitTenantVerifierFn, type AuthkitTenantVerifierOptions, authkitTenantVerifier, type TenantTokenIssuer, type TenantVerifierInput, type VerifiedTenant, } from './src/service_tenants/tenant_verifier.js';
|
package/build/index.js
CHANGED
|
@@ -137,6 +137,7 @@ export { OidcService } from './src/provider/oidc_service.js';
|
|
|
137
137
|
export { checkClientRegistration, classifyRedirect, RegistrationPolicyError, } from './src/provider/registration_policy.js';
|
|
138
138
|
export { registerOidcRoutes } from './src/register_routes.js';
|
|
139
139
|
export { ensureAuthkitSchema, ensureAuthkitTables } from './src/schema/ensure.js';
|
|
140
|
+
export { AuthkitSchemaError } from './src/schema/tables_location.js';
|
|
140
141
|
export { AuthkitTenantVerifier, authkitTenantVerifier, } from './src/service_tenants/tenant_verifier.js';
|
|
141
142
|
export { authkitTenantLabels, } from './src/tenancy/tenant_labels.js';
|
|
142
143
|
export { stubsRoot } from './stubs/main.js';
|
|
@@ -22,7 +22,7 @@ import { authkitDoctorChecks } from '../src/doctor/agora_checks.js';
|
|
|
22
22
|
import { registerDoctorChecks } from '../src/doctor/agora_doctor.js';
|
|
23
23
|
import { resolveAppKey } from '../src/host/app_key.js';
|
|
24
24
|
import { resolveEffectiveKeyRotation } from '../src/host/key_rotation.js';
|
|
25
|
-
import { RuntimeSettings } from '../src/host/runtime_settings.js';
|
|
25
|
+
import { RuntimeSettings, settingsLocation } from '../src/host/runtime_settings.js';
|
|
26
26
|
import { KeystoreCodec } from '../src/keys/keystore_codec.js';
|
|
27
27
|
import { loadEncryptionService } from '../src/keys/keystore_crypto.js';
|
|
28
28
|
import { KeystoreManager, resolveKeystoreVault } from '../src/keys/keystore_manager.js';
|
|
@@ -30,6 +30,7 @@ import { KeyRotationScheduler } from '../src/provider/key_rotation_scheduler.js'
|
|
|
30
30
|
import { KeystoreReloadPoller } from '../src/provider/keystore_reload.js';
|
|
31
31
|
import { OidcService } from '../src/provider/oidc_service.js';
|
|
32
32
|
import { makeSingleFlightLock } from '../src/provider/single_flight_lock.js';
|
|
33
|
+
import { AuthkitSchemaError } from '../src/schema/tables_location.js';
|
|
33
34
|
export default class AuthkitServerProvider {
|
|
34
35
|
app;
|
|
35
36
|
constructor(app) {
|
|
@@ -286,6 +287,7 @@ export default class AuthkitServerProvider {
|
|
|
286
287
|
const { ensureAuthkitSchema } = await import('../src/schema/ensure.js');
|
|
287
288
|
const report = await ensureAuthkitSchema(db, {
|
|
288
289
|
connection: config.schema.connection,
|
|
290
|
+
schema: config.schema.name,
|
|
289
291
|
accountTable: config.accountStore?.accountTable,
|
|
290
292
|
accountConnection: config.accountStore?.connectionName,
|
|
291
293
|
tables: config.personalAgents?.extensions?.flatMap((e) => e.tables ?? []),
|
|
@@ -306,6 +308,10 @@ export default class AuthkitServerProvider {
|
|
|
306
308
|
}
|
|
307
309
|
}
|
|
308
310
|
catch (error) {
|
|
311
|
+
// `schema.name` que o banco não aceita (SQLite, schema que o role não pode criar):
|
|
312
|
+
// derruba o boot com a mensagem — nunca segue com tabelas fora do schema pedido.
|
|
313
|
+
if (error instanceof AuthkitSchemaError)
|
|
314
|
+
throw error;
|
|
309
315
|
const logger = await this.app.container.make('logger').catch(() => null);
|
|
310
316
|
logger?.warn({ err: error }, 'authkit: schema auto-manage failed — features degrade gracefully; ' +
|
|
311
317
|
'run ensureAuthkitSchema() in a migration or fix DB connectivity');
|
|
@@ -365,10 +371,15 @@ export default class AuthkitServerProvider {
|
|
|
365
371
|
.resolve(this.app, value)
|
|
366
372
|
.catch(() => null))
|
|
367
373
|
: null;
|
|
368
|
-
const connection = config?.schema?.connection;
|
|
369
374
|
// RuntimeSettings sem request (mesmo padrão de expire_scan_command / security_notice_service).
|
|
370
|
-
const settings = new RuntimeSettings(db,
|
|
371
|
-
|
|
375
|
+
const settings = new RuntimeSettings(db, settingsLocation(config));
|
|
376
|
+
// Com `schema.name`, a chave do lock leva o schema: duas instalações em schemas
|
|
377
|
+
// diferentes do mesmo banco não disputam o mesmo lock.
|
|
378
|
+
const schemaName = config?.schema?.name;
|
|
379
|
+
const withLock = makeSingleFlightLock({
|
|
380
|
+
key: schemaName ? `authkit:${schemaName}:keys:rotate` : 'authkit:keys:rotate',
|
|
381
|
+
ttlMs: 5 * 60_000,
|
|
382
|
+
});
|
|
372
383
|
const scheduler = new KeyRotationScheduler({
|
|
373
384
|
policy: () => resolveEffectiveKeyRotation(settings),
|
|
374
385
|
ageDays: () => svc.keystoreAgeDays(),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PasswordManager } from '../password/password_manager.js';
|
|
2
|
-
import { authkitTablesClient, BIND_TABLES_LOCATION, } from '../schema/tables_location.js';
|
|
2
|
+
import { authkitTable, authkitTablesClient, BIND_TABLES_LOCATION, } from '../schema/tables_location.js';
|
|
3
3
|
import { buildCore } from './lucid_store/core.js';
|
|
4
4
|
import { buildLoginMethods, supportsLoginMethodsColumn } from './lucid_store/login_methods.js';
|
|
5
5
|
import { buildMfa } from './lucid_store/mfa.js';
|
|
@@ -178,7 +178,7 @@ export function lucidAccountStore(Model, options = {}) {
|
|
|
178
178
|
return authkitTablesClient(db, tablesLocation);
|
|
179
179
|
};
|
|
180
180
|
const orgModels = () => explicitOrgModels ??
|
|
181
|
-
(tablesLocation ? organizationModelsOn(tablesLocation
|
|
181
|
+
(tablesLocation ? organizationModelsOn(tablesLocation) : defaultOrganizationModels);
|
|
182
182
|
const OrgModels = orgModelsOption === false ? undefined : orgModels;
|
|
183
183
|
// RP do WebAuthn: usado nas cerimônias. Default do rpName cai no mfaIssuer.
|
|
184
184
|
const webauthn = options.webauthn ?? {
|
|
@@ -204,6 +204,7 @@ export function lucidAccountStore(Model, options = {}) {
|
|
|
204
204
|
emailVerificationTtlHours: emailTokens.verificationTtlHours,
|
|
205
205
|
emailChangeTtlHours: emailTokens.changeTtlHours,
|
|
206
206
|
tablesClient,
|
|
207
|
+
tableName: (name) => authkitTable(tablesLocation ?? undefined, name),
|
|
207
208
|
// Encripta o segredo antes de persistir (no-op sem encrypter).
|
|
208
209
|
sealSecret: (secret) => (encrypter ? encrypter.encrypt(secret) : secret),
|
|
209
210
|
// Decripta o segredo armazenado; retorna null em falha/adulteração (no-op sem encrypter).
|
|
@@ -345,7 +346,7 @@ export function lucidAccountStore(Model, options = {}) {
|
|
|
345
346
|
// Não-enumerável: o `defineConfig` informa `schema.connection` por aqui.
|
|
346
347
|
Object.defineProperty(store, BIND_TABLES_LOCATION, {
|
|
347
348
|
value: (location) => {
|
|
348
|
-
tablesLocation = { connection: location.connection };
|
|
349
|
+
tablesLocation = { connection: location.connection, schema: location.schema };
|
|
349
350
|
},
|
|
350
351
|
});
|
|
351
352
|
return store;
|
|
@@ -19,7 +19,7 @@ import { buildMfaStateRepo, generateRecoveryCode, hashesEqual, sha256, } from '.
|
|
|
19
19
|
const TOTP_PERIOD = 30;
|
|
20
20
|
export function buildMfa(ctx) {
|
|
21
21
|
const { Model, mfaIssuer, recoveryCodeCount, sealSecret, openSecret } = ctx;
|
|
22
|
-
const repo = buildMfaStateRepo(Model, ctx.tablesClient);
|
|
22
|
+
const repo = buildMfaStateRepo(Model, ctx.tablesClient, ctx.tableName);
|
|
23
23
|
return {
|
|
24
24
|
async getMfaState(accountId) {
|
|
25
25
|
const state = await repo.read(accountId);
|
|
@@ -11,8 +11,8 @@ import type { DateTime } from 'luxon';
|
|
|
11
11
|
* sintoma aparece longe da causa.
|
|
12
12
|
*
|
|
13
13
|
* Use com `organizationModels: true` em {@link lucidAccountStore}. Eles seguem a
|
|
14
|
-
* conexão das tabelas do authkit (`schema.connection`
|
|
15
|
-
* todas as tabelas lib-owned — ver {@link organizationModelsOn}). O caminho
|
|
14
|
+
* conexão e o schema das tabelas do authkit (`schema.connection`/`schema.name`
|
|
15
|
+
* do `defineConfig`, como todas as tabelas lib-owned — ver {@link organizationModelsOn}). O caminho
|
|
16
16
|
* explícito (`{ OrgModel, MemberModel, InvitationModel }`) continua valendo como
|
|
17
17
|
* escape hatch; aí a conexão é a que os models do host declaram.
|
|
18
18
|
*
|
|
@@ -72,7 +72,11 @@ export type OrganizationModels = {
|
|
|
72
72
|
InvitationModel: any;
|
|
73
73
|
};
|
|
74
74
|
/**
|
|
75
|
-
* Os models default
|
|
76
|
-
*
|
|
75
|
+
* Os models default onde vivem as tabelas do authkit: subclasses com `static connection`
|
|
76
|
+
* (`schema.connection`) e `static table` qualificada (`schema.name`), memoizadas por
|
|
77
|
+
* local. Sem conexão nem schema → os próprios defaults (conexão primária).
|
|
77
78
|
*/
|
|
78
|
-
export declare function organizationModelsOn(
|
|
79
|
+
export declare function organizationModelsOn(location: {
|
|
80
|
+
connection?: string;
|
|
81
|
+
schema?: string;
|
|
82
|
+
}): OrganizationModels;
|
|
@@ -16,8 +16,8 @@ import { BaseModel, column } from '@adonisjs/lucid/orm';
|
|
|
16
16
|
* sintoma aparece longe da causa.
|
|
17
17
|
*
|
|
18
18
|
* Use com `organizationModels: true` em {@link lucidAccountStore}. Eles seguem a
|
|
19
|
-
* conexão das tabelas do authkit (`schema.connection`
|
|
20
|
-
* todas as tabelas lib-owned — ver {@link organizationModelsOn}). O caminho
|
|
19
|
+
* conexão e o schema das tabelas do authkit (`schema.connection`/`schema.name`
|
|
20
|
+
* do `defineConfig`, como todas as tabelas lib-owned — ver {@link organizationModelsOn}). O caminho
|
|
21
21
|
* explícito (`{ OrgModel, MemberModel, InvitationModel }`) continua valendo como
|
|
22
22
|
* escape hatch; aí a conexão é a que os models do host declaram.
|
|
23
23
|
*
|
|
@@ -112,30 +112,34 @@ export const defaultOrganizationModels = {
|
|
|
112
112
|
MemberModel: AuthOrganizationMember,
|
|
113
113
|
InvitationModel: AuthOrganizationInvitation,
|
|
114
114
|
};
|
|
115
|
-
const
|
|
115
|
+
const modelsByLocation = new Map();
|
|
116
116
|
/**
|
|
117
|
-
* Os models default
|
|
118
|
-
*
|
|
117
|
+
* Os models default onde vivem as tabelas do authkit: subclasses com `static connection`
|
|
118
|
+
* (`schema.connection`) e `static table` qualificada (`schema.name`), memoizadas por
|
|
119
|
+
* local. Sem conexão nem schema → os próprios defaults (conexão primária).
|
|
119
120
|
*/
|
|
120
|
-
export function organizationModelsOn(
|
|
121
|
-
|
|
121
|
+
export function organizationModelsOn(location) {
|
|
122
|
+
const { connection, schema } = location;
|
|
123
|
+
if (!connection && !schema)
|
|
122
124
|
return defaultOrganizationModels;
|
|
123
|
-
|
|
125
|
+
const key = `${connection ?? ''}\x00${schema ?? ''}`;
|
|
126
|
+
let models = modelsByLocation.get(key);
|
|
124
127
|
if (!models) {
|
|
128
|
+
const table = (name) => (schema ? `${schema}.${name}` : name);
|
|
125
129
|
class OrgModel extends AuthOrganization {
|
|
126
|
-
static table = 'auth_organizations';
|
|
130
|
+
static table = table('auth_organizations');
|
|
127
131
|
static connection = connection;
|
|
128
132
|
}
|
|
129
133
|
class MemberModel extends AuthOrganizationMember {
|
|
130
|
-
static table = 'auth_organization_members';
|
|
134
|
+
static table = table('auth_organization_members');
|
|
131
135
|
static connection = connection;
|
|
132
136
|
}
|
|
133
137
|
class InvitationModel extends AuthOrganizationInvitation {
|
|
134
|
-
static table = 'auth_organization_invitations';
|
|
138
|
+
static table = table('auth_organization_invitations');
|
|
135
139
|
static connection = connection;
|
|
136
140
|
}
|
|
137
141
|
models = { OrgModel, MemberModel, InvitationModel };
|
|
138
|
-
|
|
142
|
+
modelsByLocation.set(key, models);
|
|
139
143
|
}
|
|
140
144
|
return models;
|
|
141
145
|
}
|
|
@@ -17,8 +17,10 @@ export function buildPasswordHistory(ctx, db) {
|
|
|
17
17
|
* Lucid expõe `db.query().from(table)` para SELECT e `db.table(table).insert()` para INSERT.
|
|
18
18
|
* Para DELETE usamos `db.query().from(table)`.
|
|
19
19
|
*/
|
|
20
|
+
// Lido a cada uso: o `defineConfig` informa o schema depois de o store existir.
|
|
21
|
+
const table = () => ctx.tableName ? ctx.tableName('auth_password_history') : 'auth_password_history';
|
|
20
22
|
function qFrom() {
|
|
21
|
-
return db.query().from(
|
|
23
|
+
return db.query().from(table());
|
|
22
24
|
}
|
|
23
25
|
return {
|
|
24
26
|
async isPasswordReused(accountId, plainPassword, count, nativeVerify) {
|
|
@@ -51,7 +53,7 @@ export function buildPasswordHistory(ctx, db) {
|
|
|
51
53
|
},
|
|
52
54
|
async recordPasswordHistory(accountId, oldHash) {
|
|
53
55
|
try {
|
|
54
|
-
await db.table(
|
|
56
|
+
await db.table(table()).insert({
|
|
55
57
|
account_id: accountId,
|
|
56
58
|
password_hash: oldHash,
|
|
57
59
|
created_at: DateTime.now().toISO(),
|
|
@@ -84,6 +84,11 @@ export interface LucidStoreContext {
|
|
|
84
84
|
* → the model's connection (a store used without `defineConfig`).
|
|
85
85
|
*/
|
|
86
86
|
tablesClient?: () => any;
|
|
87
|
+
/**
|
|
88
|
+
* Nome de uma tabela do authkit como os statements a escrevem: com `schema.name`,
|
|
89
|
+
* `schema.tabela` (o knex cita cada parte). Ausente → o nome puro.
|
|
90
|
+
*/
|
|
91
|
+
tableName?: (name: string) => string;
|
|
87
92
|
}
|
|
88
93
|
export declare const sha256: (value: string) => string;
|
|
89
94
|
/** Recovery code legível: 10 chars hex em duas metades (ex.: a1b2c-3d4e5). */
|
|
@@ -121,7 +126,7 @@ export interface MfaStatePatch {
|
|
|
121
126
|
* JSON (string) na escrita e desserializado na leitura (aceita tanto string —
|
|
122
127
|
* sqlite/text — quanto array já parseado — pg jsonb).
|
|
123
128
|
*/
|
|
124
|
-
export declare function buildMfaStateRepo(Model: any, tablesClient?: () => any): {
|
|
129
|
+
export declare function buildMfaStateRepo(Model: any, tablesClient?: () => any, tableName?: (name: string) => string): {
|
|
125
130
|
/** Lê o estado de MFA da conta; null se não há linha em `auth_mfa`. */
|
|
126
131
|
read(accountId: string): Promise<MfaState | null>;
|
|
127
132
|
/**
|
|
@@ -49,8 +49,9 @@ export function hashesEqual(a, b) {
|
|
|
49
49
|
* JSON (string) na escrita e desserializado na leitura (aceita tanto string —
|
|
50
50
|
* sqlite/text — quanto array já parseado — pg jsonb).
|
|
51
51
|
*/
|
|
52
|
-
export function buildMfaStateRepo(Model, tablesClient) {
|
|
53
|
-
|
|
52
|
+
export function buildMfaStateRepo(Model, tablesClient, tableName = (name) => name) {
|
|
53
|
+
// Lido a cada uso: o `defineConfig` informa o schema depois de o store existir.
|
|
54
|
+
const table = () => tableName('auth_mfa');
|
|
54
55
|
/** QueryClient das tabelas do authkit (equivale ao `db` do password_history). */
|
|
55
56
|
function client() {
|
|
56
57
|
return tablesClient ? tablesClient() : Model.query().client;
|
|
@@ -92,7 +93,7 @@ export function buildMfaStateRepo(Model, tablesClient) {
|
|
|
92
93
|
return {
|
|
93
94
|
/** Lê o estado de MFA da conta; null se não há linha em `auth_mfa`. */
|
|
94
95
|
async read(accountId) {
|
|
95
|
-
const row = await client().from(
|
|
96
|
+
const row = await client().from(table()).where('account_id', accountId).first();
|
|
96
97
|
if (!row)
|
|
97
98
|
return null;
|
|
98
99
|
return {
|
|
@@ -126,17 +127,17 @@ export function buildMfaStateRepo(Model, tablesClient) {
|
|
|
126
127
|
}
|
|
127
128
|
if ('lastTotpStep' in patch)
|
|
128
129
|
update.last_totp_step = patch.lastTotpStep ?? null;
|
|
129
|
-
const affected = await client().from(
|
|
130
|
+
const affected = await client().from(table()).where('account_id', accountId).update(update);
|
|
130
131
|
// knex update retorna o nº de linhas afetadas (0 = linha ainda não existe).
|
|
131
132
|
if (!affected) {
|
|
132
133
|
await client()
|
|
133
|
-
.table(
|
|
134
|
+
.table(table())
|
|
134
135
|
.insert({ account_id: accountId, ...update });
|
|
135
136
|
}
|
|
136
137
|
},
|
|
137
138
|
/** Remove o estado de MFA da conta (disable / reset total). No-op se ausente. */
|
|
138
139
|
async clear(accountId) {
|
|
139
|
-
await client().from(
|
|
140
|
+
await client().from(table()).where('account_id', accountId).delete();
|
|
140
141
|
},
|
|
141
142
|
};
|
|
142
143
|
}
|
|
@@ -8,7 +8,7 @@ export function buildWebauthn(ctx, Credential, webauthn, ceremonies) {
|
|
|
8
8
|
const { Model } = ctx;
|
|
9
9
|
// Estado de MFA é LIB-OWNED (tabela `auth_mfa`): registrar uma passkey habilita
|
|
10
10
|
// o MFA gravando `mfa_enabled_at` em `auth_mfa`, não numa coluna do model.
|
|
11
|
-
const mfaState = buildMfaStateRepo(Model, ctx.tablesClient);
|
|
11
|
+
const mfaState = buildMfaStateRepo(Model, ctx.tablesClient, ctx.tableName);
|
|
12
12
|
return {
|
|
13
13
|
async generatePasskeyRegistrationOptions(accountId) {
|
|
14
14
|
const row = await Model.find(accountId);
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import type { Database } from '@adonisjs/lucid/database';
|
|
2
2
|
import type { EnumeratedArtifact, OidcAdapter, OidcPayload } from './adapter_contract.js';
|
|
3
|
+
/** Nome da tabela (sem schema). O adapter recebe o nome já qualificado pelo factory. */
|
|
4
|
+
export declare const OIDC_PAYLOADS_TABLE = "authkit_oidc_payloads";
|
|
3
5
|
export declare class DatabaseAdapter implements OidcAdapter {
|
|
4
6
|
#private;
|
|
5
7
|
private name;
|
|
6
8
|
private db;
|
|
7
|
-
|
|
9
|
+
/** `authkit_oidc_payloads`, qualificada com `schema.name` quando há schema. */
|
|
10
|
+
private table;
|
|
11
|
+
constructor(name: string, db: Database,
|
|
12
|
+
/** `authkit_oidc_payloads`, qualificada com `schema.name` quando há schema. */
|
|
13
|
+
table?: string);
|
|
8
14
|
upsert(id: string, payload: OidcPayload, expiresIn: number): Promise<void>;
|
|
9
15
|
renewSession(id: string, expiresIn: number): Promise<boolean>;
|
|
10
16
|
find(id: string): Promise<OidcPayload | undefined>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
/** Nome da tabela (sem schema). O adapter recebe o nome já qualificado pelo factory. */
|
|
2
|
+
export const OIDC_PAYLOADS_TABLE = 'authkit_oidc_payloads';
|
|
2
3
|
/** Intervalo mínimo, por conexão, entre duas limpezas das linhas expiradas. */
|
|
3
4
|
const PRUNE_INTERVAL_MS = 10 * 60_000;
|
|
4
5
|
/** Quanto tempo a ausência da coluna `account_id` vale antes de re-checar. */
|
|
@@ -6,11 +7,17 @@ const ACCOUNT_COLUMN_RECHECK_MS = 60_000;
|
|
|
6
7
|
/** Tamanho máximo de uma lista `IN (...)`. */
|
|
7
8
|
const IN_CHUNK = 500;
|
|
8
9
|
const connectionStates = new WeakMap();
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
/** Estado por conexão E tabela (duas instalações em schemas diferentes não se misturam). */
|
|
11
|
+
function connectionState(db, table) {
|
|
12
|
+
let byTable = connectionStates.get(db);
|
|
13
|
+
if (!byTable) {
|
|
14
|
+
byTable = new Map();
|
|
15
|
+
connectionStates.set(db, byTable);
|
|
16
|
+
}
|
|
17
|
+
let state = byTable.get(table);
|
|
11
18
|
if (!state) {
|
|
12
19
|
state = { accountColumn: null, accountColumnRecheckAt: 0, nextPruneAt: 0 };
|
|
13
|
-
|
|
20
|
+
byTable.set(table, state);
|
|
14
21
|
}
|
|
15
22
|
return state;
|
|
16
23
|
}
|
|
@@ -29,12 +36,16 @@ function chunks(items) {
|
|
|
29
36
|
export class DatabaseAdapter {
|
|
30
37
|
name;
|
|
31
38
|
db;
|
|
32
|
-
|
|
39
|
+
table;
|
|
40
|
+
constructor(name, db,
|
|
41
|
+
/** `authkit_oidc_payloads`, qualificada com `schema.name` quando há schema. */
|
|
42
|
+
table = OIDC_PAYLOADS_TABLE) {
|
|
33
43
|
this.name = name;
|
|
34
44
|
this.db = db;
|
|
45
|
+
this.table = table;
|
|
35
46
|
}
|
|
36
47
|
#query() {
|
|
37
|
-
return this.db.query().from(
|
|
48
|
+
return this.db.query().from(this.table).where('model_name', this.name);
|
|
38
49
|
}
|
|
39
50
|
/**
|
|
40
51
|
* Valor de `expires_at` (gravação e comparação) para o dialeto da conexão. No
|
|
@@ -43,7 +54,7 @@ export class DatabaseAdapter {
|
|
|
43
54
|
* driver serializa no formato do banco — o MySQL recusa o ISO com `Z`.
|
|
44
55
|
*/
|
|
45
56
|
#timestamp(ms) {
|
|
46
|
-
const state = connectionState(this.db);
|
|
57
|
+
const state = connectionState(this.db, this.table);
|
|
47
58
|
if (state.dialect === undefined) {
|
|
48
59
|
// O factory passa o manager (`Database`) ou um `QueryClient` de conexão nomeada.
|
|
49
60
|
const db = this.db;
|
|
@@ -66,13 +77,13 @@ export class DatabaseAdapter {
|
|
|
66
77
|
* re-checado, para pegar o ALTER do boot que rodou depois do primeiro request.
|
|
67
78
|
*/
|
|
68
79
|
async #hasAccountColumn() {
|
|
69
|
-
const state = connectionState(this.db);
|
|
80
|
+
const state = connectionState(this.db, this.table);
|
|
70
81
|
if (state.accountColumn === true)
|
|
71
82
|
return true;
|
|
72
83
|
if (state.accountColumn === false && state.accountColumnRecheckAt > Date.now())
|
|
73
84
|
return false;
|
|
74
85
|
try {
|
|
75
|
-
await this.db.query().from(
|
|
86
|
+
await this.db.query().from(this.table).select('account_id').limit(1);
|
|
76
87
|
state.accountColumn = true;
|
|
77
88
|
}
|
|
78
89
|
catch {
|
|
@@ -88,7 +99,7 @@ export class DatabaseAdapter {
|
|
|
88
99
|
* do caminho do request e nunca lança.
|
|
89
100
|
*/
|
|
90
101
|
#maybePrune() {
|
|
91
|
-
const state = connectionState(this.db);
|
|
102
|
+
const state = connectionState(this.db, this.table);
|
|
92
103
|
const now = Date.now();
|
|
93
104
|
if (state.nextPruneAt > now)
|
|
94
105
|
return;
|
|
@@ -96,7 +107,7 @@ export class DatabaseAdapter {
|
|
|
96
107
|
Promise.resolve()
|
|
97
108
|
.then(() => this.db
|
|
98
109
|
.query()
|
|
99
|
-
.from(
|
|
110
|
+
.from(this.table)
|
|
100
111
|
.whereNotNull('expires_at')
|
|
101
112
|
.where('expires_at', '<=', this.#timestamp(now))
|
|
102
113
|
.delete())
|
|
@@ -125,7 +136,7 @@ export class DatabaseAdapter {
|
|
|
125
136
|
await this.#query().where('id', id).update(row);
|
|
126
137
|
}
|
|
127
138
|
else {
|
|
128
|
-
await this.db.table(
|
|
139
|
+
await this.db.table(this.table).insert(row);
|
|
129
140
|
}
|
|
130
141
|
this.#maybePrune();
|
|
131
142
|
}
|
|
@@ -185,7 +196,7 @@ export class DatabaseAdapter {
|
|
|
185
196
|
await this.#query().where('id', id).delete();
|
|
186
197
|
}
|
|
187
198
|
async revokeByGrantId(grantId) {
|
|
188
|
-
await this.db.query().from(
|
|
199
|
+
await this.db.query().from(this.table).where('grant_id', grantId).delete();
|
|
189
200
|
}
|
|
190
201
|
/**
|
|
191
202
|
* Enumeração genérica dos artefatos do model deste adapter (id + payload).
|
|
@@ -8,6 +8,8 @@ export interface AdapterResolveContext {
|
|
|
8
8
|
* on. A database adapter without its own `connection` uses it.
|
|
9
9
|
*/
|
|
10
10
|
connection?: string;
|
|
11
|
+
/** `schema.name` of `defineConfig`. A database adapter without its own `schema` uses it. */
|
|
12
|
+
schema?: string;
|
|
11
13
|
}
|
|
12
14
|
export interface AdapterFactory {
|
|
13
15
|
resolver(app: ApplicationService, context?: AdapterResolveContext): Promise<OidcAdapterClass>;
|
|
@@ -48,6 +50,11 @@ export interface DatabaseAdapterConfig {
|
|
|
48
50
|
* de todas as tabelas do authkit), e sem ela a primária.
|
|
49
51
|
*/
|
|
50
52
|
connection?: string;
|
|
53
|
+
/**
|
|
54
|
+
* Schema do banco da tabela `authkit_oidc_payloads` (MySQL: o database). Default:
|
|
55
|
+
* `schema.name` do `defineConfig`. Vai escrito em cada statement — nunca via searchPath.
|
|
56
|
+
*/
|
|
57
|
+
schema?: string;
|
|
51
58
|
}
|
|
52
59
|
export declare const adapters: {
|
|
53
60
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assertSchemaSupported, authkitTable, validateSchemaName, } from '../schema/tables_location.js';
|
|
2
|
+
import { DatabaseAdapter, OIDC_PAYLOADS_TABLE } from './database_adapter.js';
|
|
2
3
|
import { RedisAdapter } from './redis_adapter.js';
|
|
3
4
|
/**
|
|
4
5
|
* Modelos do oidc-provider de vida curta, amarrados ao ciclo da sessão de
|
|
@@ -70,10 +71,13 @@ export const adapters = {
|
|
|
70
71
|
async resolver(app, context) {
|
|
71
72
|
const db = await app.container.make('lucid.db');
|
|
72
73
|
const connection = config.connection ?? context?.connection;
|
|
74
|
+
const schema = validateSchemaName(config.schema ?? context?.schema, 'adapters.database({ schema })');
|
|
73
75
|
const conn = connection ? db.connection(connection) : db;
|
|
76
|
+
assertSchemaSupported(connection ? conn : db.connection(), schema);
|
|
77
|
+
const table = authkitTable({ schema }, OIDC_PAYLOADS_TABLE);
|
|
74
78
|
return class extends DatabaseAdapter {
|
|
75
79
|
constructor(name) {
|
|
76
|
-
super(name, conn);
|
|
80
|
+
super(name, conn, table);
|
|
77
81
|
}
|
|
78
82
|
};
|
|
79
83
|
},
|
|
@@ -46,8 +46,12 @@ export interface LiveGrantFilter {
|
|
|
46
46
|
export declare class DelegationStore {
|
|
47
47
|
#private;
|
|
48
48
|
private conn;
|
|
49
|
+
/** Nome da tabela como os statements o escrevem (`schema.tabela` com `schema.name`). */
|
|
50
|
+
private t;
|
|
49
51
|
/** `conn` = uma conexão Lucid (`db.connection(name?)`) — ou uma transação. */
|
|
50
|
-
constructor(conn: () => any
|
|
52
|
+
constructor(conn: () => any,
|
|
53
|
+
/** Nome da tabela como os statements o escrevem (`schema.tabela` com `schema.name`). */
|
|
54
|
+
t?: (name: string) => string);
|
|
51
55
|
insertDevice(row: Omit<DeviceCodeRow, 'accountId' | 'grantId' | 'lastPolledAt'>): Promise<void>;
|
|
52
56
|
findDeviceByCodeHash(hash: string): Promise<DeviceCodeRow | null>;
|
|
53
57
|
findDeviceByUserCode(userCode: string): Promise<DeviceCodeRow | null>;
|