@adonis-agora/authkit-server 1.0.0-rc.10 → 1.0.0-rc.12
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 +33 -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 +16 -4
- package/build/src/accounts/account_store.d.ts +7 -7
- package/build/src/accounts/lucid_account_store.d.ts +3 -3
- package/build/src/accounts/lucid_account_store.js +43 -17
- package/build/src/accounts/lucid_store/mfa.js +3 -3
- package/build/src/accounts/lucid_store/organization_models.d.ts +18 -3
- package/build/src/accounts/lucid_store/organization_models.js +35 -3
- package/build/src/accounts/lucid_store/organizations.d.ts +2 -2
- package/build/src/accounts/lucid_store/organizations.js +35 -32
- package/build/src/accounts/lucid_store/password_hygiene.js +4 -2
- package/build/src/accounts/lucid_store/shared.d.ts +16 -3
- package/build/src/accounts/lucid_store/shared.js +13 -10
- 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 +20 -2
- package/build/src/adapters/factory.js +8 -4
- 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 +5 -5
- package/build/src/define_config.d.ts +12 -1
- package/build/src/define_config.js +34 -3
- package/build/src/doctor/agora_checks.js +13 -0
- package/build/src/doctor/checks.js +2 -2
- package/build/src/doctor/db_schema_check.d.ts +6 -0
- package/build/src/doctor/db_schema_check.js +87 -0
- package/build/src/host/admin_sessions_service.js +6 -2
- package/build/src/host/runtime_settings.d.ts +21 -6
- package/build/src/host/runtime_settings.js +29 -13
- 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 +19 -1
- package/build/src/schema/ensure.js +54 -30
- package/build/src/schema/tables_location.d.ts +56 -0
- package/build/src/schema/tables_location.js +86 -0
- package/package.json +3 -3
- package/skills/authkit-idp-setup/SKILL.md +13 -7
|
@@ -24,9 +24,26 @@
|
|
|
24
24
|
* ```
|
|
25
25
|
*/
|
|
26
26
|
import type { AuthkitTable } from '../agents/extension.js';
|
|
27
|
+
/** Os nomes (sem schema) das tabelas que {@link ensureAuthkitSchema} garante. */
|
|
28
|
+
export declare const AUTHKIT_TABLE_NAMES: readonly string[];
|
|
27
29
|
export interface EnsureSchemaOptions {
|
|
28
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Conexão Lucid das tabelas do authkit (`schema.connection`). Default: conexão
|
|
32
|
+
* primária. Ignorada quando `db` já é um client (o `this.db` de uma migration).
|
|
33
|
+
*/
|
|
29
34
|
connection?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Conexão Lucid do model da conta, onde vive a tabela da conta (a coluna
|
|
37
|
+
* `login_methods`). Default: a conexão primária — NÃO a das tabelas do authkit.
|
|
38
|
+
* O provider passa a do `accountStore` no boot. Ignorada quando `db` já é um client.
|
|
39
|
+
*/
|
|
40
|
+
accountConnection?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Database schema of the tables (MySQL: the database) — `schema.name`. Every statement
|
|
43
|
+
* names it (`"auth"."auth_settings"`), and it is created when missing. Never a
|
|
44
|
+
* `searchPath`. Ignored for the account table, which stays where the app put it.
|
|
45
|
+
*/
|
|
46
|
+
schema?: string;
|
|
30
47
|
/**
|
|
31
48
|
* Tabelas de `personalAgents.extensions` (`tables` de cada uma), garantidas junto. O provider
|
|
32
49
|
* passa as do config no boot.
|
|
@@ -64,6 +81,7 @@ export interface EnsureSchemaReport {
|
|
|
64
81
|
*/
|
|
65
82
|
export declare function ensureAuthkitTables(db: any, tables: readonly AuthkitTable[], options?: {
|
|
66
83
|
connection?: string;
|
|
84
|
+
schema?: string;
|
|
67
85
|
}): Promise<Pick<EnsureSchemaReport, 'created' | 'altered' | 'indexed'>>;
|
|
68
86
|
/**
|
|
69
87
|
* Garante que as tabelas do authkit existem e têm todas as colunas que esta
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* }
|
|
24
24
|
* ```
|
|
25
25
|
*/
|
|
26
|
+
import { assertSchemaSupported, authkitTable, authkitTablesClient, createDbSchema, validateSchemaName, } from './tables_location.js';
|
|
26
27
|
/** Busca por conta (`account_id`) e limpeza dos expirados (`expires_at`). */
|
|
27
28
|
const OIDC_PAYLOAD_INDEXES = [
|
|
28
29
|
{ name: 'authkit_oidc_payloads_account_idx', columns: ['model_name', 'account_id'] },
|
|
@@ -138,12 +139,12 @@ const TABLES = [
|
|
|
138
139
|
},
|
|
139
140
|
{
|
|
140
141
|
name: 'auth_organization_members',
|
|
141
|
-
create: (t) => {
|
|
142
|
+
create: (t, ctx) => {
|
|
142
143
|
t.string('id').primary();
|
|
143
144
|
t.string('organization_id')
|
|
144
145
|
.notNullable()
|
|
145
146
|
.references('id')
|
|
146
|
-
.inTable('auth_organizations')
|
|
147
|
+
.inTable(ctx.table('auth_organizations'))
|
|
147
148
|
.onDelete('CASCADE');
|
|
148
149
|
t.string('account_id').notNullable();
|
|
149
150
|
t.string('role').notNullable().defaultTo('member');
|
|
@@ -185,12 +186,12 @@ const TABLES = [
|
|
|
185
186
|
},
|
|
186
187
|
{
|
|
187
188
|
name: 'auth_organization_invitations',
|
|
188
|
-
create: (t) => {
|
|
189
|
+
create: (t, ctx) => {
|
|
189
190
|
t.string('id').primary();
|
|
190
191
|
t.string('organization_id')
|
|
191
192
|
.notNullable()
|
|
192
193
|
.references('id')
|
|
193
|
-
.inTable('auth_organizations')
|
|
194
|
+
.inTable(ctx.table('auth_organizations'))
|
|
194
195
|
.onDelete('CASCADE');
|
|
195
196
|
t.string('email').notNullable();
|
|
196
197
|
t.string('role').notNullable().defaultTo('member');
|
|
@@ -334,6 +335,8 @@ const TABLES = [
|
|
|
334
335
|
},
|
|
335
336
|
},
|
|
336
337
|
];
|
|
338
|
+
/** Os nomes (sem schema) das tabelas que {@link ensureAuthkitSchema} garante. */
|
|
339
|
+
export const AUTHKIT_TABLE_NAMES = TABLES.map((t) => t.name);
|
|
337
340
|
/** Linhas de um `rawQuery`, no formato de cada driver (pg, mysql2, sqlite). */
|
|
338
341
|
function catalogRows(result) {
|
|
339
342
|
if (Array.isArray(result))
|
|
@@ -419,7 +422,10 @@ async function columnExists(conn, table, column) {
|
|
|
419
422
|
async function indexExists(conn, table, name) {
|
|
420
423
|
const dialect = conn.dialect?.name;
|
|
421
424
|
if (dialect === 'postgres' || dialect === 'redshift') {
|
|
422
|
-
|
|
425
|
+
// Índice mora no schema da tabela: com schema explícito, o nome vai qualificado.
|
|
426
|
+
const [schema] = mysqlRelation(table);
|
|
427
|
+
const relation = pgRelation(schema ? `${schema}.${name}` : name);
|
|
428
|
+
const found = catalogRows(await conn.rawQuery('select to_regclass(?) as oid', [relation]));
|
|
423
429
|
return found[0]?.oid != null;
|
|
424
430
|
}
|
|
425
431
|
if (dialect === 'mysql') {
|
|
@@ -440,11 +446,12 @@ async function indexExists(conn, table, name) {
|
|
|
440
446
|
* Cria o índice quando falta. Idempotente: um índice já existente (inclusive
|
|
441
447
|
* criado por outra instância no mesmo boot) não é erro.
|
|
442
448
|
*/
|
|
443
|
-
async function ensureIndex(conn,
|
|
449
|
+
async function ensureIndex(conn, loc, name, index) {
|
|
450
|
+
const table = authkitTable(loc, name);
|
|
444
451
|
if ((await indexExists(conn, table, index.name)) === true)
|
|
445
452
|
return false;
|
|
446
453
|
try {
|
|
447
|
-
await conn.
|
|
454
|
+
await ddl(conn, loc).alterTable(name, (t) => t.index(index.columns, index.name));
|
|
448
455
|
return true;
|
|
449
456
|
}
|
|
450
457
|
catch (error) {
|
|
@@ -457,12 +464,23 @@ async function ensureIndex(conn, table, index) {
|
|
|
457
464
|
throw error;
|
|
458
465
|
}
|
|
459
466
|
}
|
|
467
|
+
/**
|
|
468
|
+
* O schema builder das tabelas do authkit: com `schema`, `withSchema` — o knex escreve
|
|
469
|
+
* `"schema"."tabela"` em todo DDL e mantém os nomes de índice derivados da tabela pura.
|
|
470
|
+
*/
|
|
471
|
+
function ddl(conn, loc) {
|
|
472
|
+
return loc.schema ? conn.schema.withSchema(loc.schema) : conn.schema;
|
|
473
|
+
}
|
|
460
474
|
/** Cria/completa as tabelas (o miolo de {@link ensureAuthkitSchema}). */
|
|
461
|
-
async function ensureTables(conn, tables, report) {
|
|
475
|
+
async function ensureTables(conn, loc, tables, report) {
|
|
476
|
+
const context = { table: (name) => authkitTable(loc, name) };
|
|
477
|
+
if (loc.schema)
|
|
478
|
+
await createDbSchema(conn, loc.schema);
|
|
462
479
|
for (const def of tables) {
|
|
463
|
-
|
|
480
|
+
const table = authkitTable(loc, def.name);
|
|
481
|
+
if (!(await tableExists(conn, table))) {
|
|
464
482
|
try {
|
|
465
|
-
await conn.
|
|
483
|
+
await ddl(conn, loc).createTable(def.name, (t) => def.create(t, context));
|
|
466
484
|
report.created.push(def.name);
|
|
467
485
|
continue;
|
|
468
486
|
}
|
|
@@ -471,20 +489,20 @@ async function ensureTables(conn, tables, report) {
|
|
|
471
489
|
* Corrida entre instâncias subindo juntas: se outra instância criou
|
|
472
490
|
* a tabela entre o probe e o CREATE, segue o jogo; senão, propaga.
|
|
473
491
|
*/
|
|
474
|
-
if (!(await tableExists(conn,
|
|
492
|
+
if (!(await tableExists(conn, table)))
|
|
475
493
|
throw error;
|
|
476
494
|
}
|
|
477
495
|
}
|
|
478
496
|
for (const [column, add] of Object.entries(def.columns)) {
|
|
479
|
-
if (await columnExists(conn,
|
|
497
|
+
if (await columnExists(conn, table, column))
|
|
480
498
|
continue;
|
|
481
|
-
await conn.
|
|
499
|
+
await ddl(conn, loc).alterTable(def.name, (t) => add(t));
|
|
482
500
|
if (!report.altered[def.name])
|
|
483
501
|
report.altered[def.name] = [];
|
|
484
502
|
report.altered[def.name].push(column);
|
|
485
503
|
}
|
|
486
504
|
for (const index of def.indexes ?? []) {
|
|
487
|
-
if (!(await ensureIndex(conn, def.name, index)))
|
|
505
|
+
if (!(await ensureIndex(conn, loc, def.name, index)))
|
|
488
506
|
continue;
|
|
489
507
|
if (!report.indexed)
|
|
490
508
|
report.indexed = {};
|
|
@@ -494,22 +512,26 @@ async function ensureTables(conn, tables, report) {
|
|
|
494
512
|
}
|
|
495
513
|
}
|
|
496
514
|
}
|
|
515
|
+
function resolveLocation(options) {
|
|
516
|
+
return {
|
|
517
|
+
connection: options.connection,
|
|
518
|
+
schema: validateSchemaName(options.schema, 'schema'),
|
|
519
|
+
};
|
|
520
|
+
}
|
|
497
521
|
/**
|
|
498
522
|
* Garante tabelas lib-owned no formato {@link AuthkitTable} — o que `ensureAuthkitSchema` faz com as
|
|
499
523
|
* do authkit: cria as que faltam, adiciona colunas e índices novos, nunca dropa nem altera. Para
|
|
500
524
|
* uma extension expor o próprio `ensure*Schema()` para migrations do host. Estável no 1.x.
|
|
501
525
|
*/
|
|
502
526
|
export async function ensureAuthkitTables(db, tables, options = {}) {
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
? db.connection(options.connection)
|
|
507
|
-
: db.connection();
|
|
527
|
+
const loc = resolveLocation(options);
|
|
528
|
+
const conn = authkitTablesClient(db, loc);
|
|
529
|
+
assertSchemaSupported(conn, loc.schema);
|
|
508
530
|
const report = {
|
|
509
531
|
created: [],
|
|
510
532
|
altered: {},
|
|
511
533
|
};
|
|
512
|
-
await ensureTables(conn, tables, report);
|
|
534
|
+
await ensureTables(conn, loc, tables, report);
|
|
513
535
|
return report;
|
|
514
536
|
}
|
|
515
537
|
/**
|
|
@@ -521,17 +543,15 @@ export async function ensureAuthkitTables(db, tables, options = {}) {
|
|
|
521
543
|
* (`this.db`, um `QueryClient`/`TransactionClient`, usado como está).
|
|
522
544
|
*/
|
|
523
545
|
export async function ensureAuthkitSchema(db, options = {}) {
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
? db.connection(options.connection)
|
|
528
|
-
: db.connection();
|
|
546
|
+
const loc = resolveLocation(options);
|
|
547
|
+
const conn = authkitTablesClient(db, loc);
|
|
548
|
+
assertSchemaSupported(conn, loc.schema);
|
|
529
549
|
const report = {
|
|
530
550
|
created: [],
|
|
531
551
|
altered: {},
|
|
532
552
|
loginMethods: { table: options.accountTable ?? 'users', ensured: false },
|
|
533
553
|
};
|
|
534
|
-
await ensureTables(conn, [...TABLES, ...(options.tables ?? [])], report);
|
|
554
|
+
await ensureTables(conn, loc, [...TABLES, ...(options.tables ?? [])], report);
|
|
535
555
|
// Coluna `login_methods` na tabela da CONTA — a tabela é HOST-owned (não está
|
|
536
556
|
// em TABLES porque o nome/shape são decisão do app), mas a LIB consome a
|
|
537
557
|
// coluna (LoginMethodsPreferenceCapability). Alguns deploys de host só migram
|
|
@@ -547,11 +567,15 @@ export async function ensureAuthkitSchema(db, options = {}) {
|
|
|
547
567
|
// `users` fixo: com a conta em `auth_users` (o nome que o próprio scaffold da
|
|
548
568
|
// lib usa), o `users` fixo acertava uma tabela qualquer de mesmo nome e a
|
|
549
569
|
// coluna ia parar no lugar errado sem erro nenhum.
|
|
570
|
+
//
|
|
571
|
+
// A tabela da conta fica na conexão do MODEL da conta, não na das tabelas do
|
|
572
|
+
// authkit (`schema.connection`).
|
|
550
573
|
const accountTable = options.accountTable ?? 'users';
|
|
551
|
-
|
|
574
|
+
const accountConn = authkitTablesClient(db, { connection: options.accountConnection });
|
|
575
|
+
if (await tableExists(accountConn, accountTable)) {
|
|
552
576
|
try {
|
|
553
|
-
if (!(await columnExists(
|
|
554
|
-
await
|
|
577
|
+
if (!(await columnExists(accountConn, accountTable, 'login_methods'))) {
|
|
578
|
+
await accountConn.schema.alterTable(accountTable, (t) => {
|
|
555
579
|
t.jsonb('login_methods').nullable();
|
|
556
580
|
});
|
|
557
581
|
report.altered[accountTable] = ['login_methods'];
|
|
@@ -568,7 +592,7 @@ export async function ensureAuthkitSchema(db, options = {}) {
|
|
|
568
592
|
* o boot não avisava nada — o silêncio que este bloco existe para acabar.
|
|
569
593
|
* (Mesma semântica do probe de `createTable` logo acima.)
|
|
570
594
|
*/
|
|
571
|
-
if (await columnExists(
|
|
595
|
+
if (await columnExists(accountConn, accountTable, 'login_methods')) {
|
|
572
596
|
report.loginMethods.ensured = true;
|
|
573
597
|
}
|
|
574
598
|
else {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where AuthKit's OWN tables live (`auth_settings`, `auth_mfa`, `auth_password_history`,
|
|
3
|
+
* the organization tables, `authkit_oidc_payloads`, the personal-agent tables, the
|
|
4
|
+
* `authkit_keystore` of the Lucid keystore, and the tables of agent extensions).
|
|
5
|
+
*
|
|
6
|
+
* The rule: every lib-owned table goes through `schema.connection` of `defineConfig`
|
|
7
|
+
* (default: the primary Lucid connection) and, with `schema.name`, lives in that database
|
|
8
|
+
* schema (MySQL: that database). The app's account table is not lib-owned: it stays on the
|
|
9
|
+
* account model's connection and table (`static connection`/`static table` of the model).
|
|
10
|
+
*
|
|
11
|
+
* The schema is written INTO every statement (`"auth"."auth_settings"`). Never put
|
|
12
|
+
* AuthKit's tables in another schema with a connection `searchPath`: knex sends
|
|
13
|
+
* `SET search_path` on the session, and behind PgBouncer in transaction mode that leaks to
|
|
14
|
+
* other clients' connections.
|
|
15
|
+
*/
|
|
16
|
+
export interface AuthkitTablesLocation {
|
|
17
|
+
/** Lucid connection name. `undefined` = the primary connection. */
|
|
18
|
+
connection?: string;
|
|
19
|
+
/** Database schema of the tables (MySQL: the database). `undefined` = the connection's default. */
|
|
20
|
+
schema?: string;
|
|
21
|
+
}
|
|
22
|
+
/** Raised for a `schema.name` the database cannot use. The provider fails the boot with it. */
|
|
23
|
+
export declare class AuthkitSchemaError extends Error {
|
|
24
|
+
constructor(message: string);
|
|
25
|
+
}
|
|
26
|
+
/** Throws {@link AuthkitSchemaError} unless `name` is a plain identifier of at most 63 chars. */
|
|
27
|
+
export declare function validateSchemaName(name: unknown, option?: string): string | undefined;
|
|
28
|
+
/** `schema.table` when a schema is set (knex quotes each part: `"schema"."table"`), else `table`. */
|
|
29
|
+
export declare function authkitTable(location: AuthkitTablesLocation | undefined, table: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* The query client for AuthKit's tables. `db` is the Lucid manager (`Database`) or a
|
|
32
|
+
* client the caller already holds (a migration's `this.db`, a transaction), which is
|
|
33
|
+
* used as is.
|
|
34
|
+
*/
|
|
35
|
+
export declare function authkitTablesClient(db: any, location?: AuthkitTablesLocation): any;
|
|
36
|
+
/** The Lucid dialect name of a client or manager (`postgres`, `mysql`, `better-sqlite3`…). */
|
|
37
|
+
export declare function dialectOf(client: any): string | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Throws {@link AuthkitSchemaError} when `schema` is set on a dialect AuthKit cannot use it on:
|
|
40
|
+
* SQLite (no schemas) and any dialect other than Postgres and MySQL. AuthKit never ignores the
|
|
41
|
+
* option nor falls back to unqualified tables.
|
|
42
|
+
*/
|
|
43
|
+
export declare function assertSchemaSupported(client: any, schema: string | undefined): void;
|
|
44
|
+
/**
|
|
45
|
+
* `CREATE SCHEMA IF NOT EXISTS` (Postgres) / `CREATE DATABASE IF NOT EXISTS` (MySQL). A role
|
|
46
|
+
* that cannot create it gets an {@link AuthkitSchemaError} naming the schema and the grant.
|
|
47
|
+
*/
|
|
48
|
+
export declare function createDbSchema(client: any, schema: string): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Hidden hook a store built by `lucidAccountStore` exposes so `defineConfig` can tell it
|
|
51
|
+
* where AuthKit's tables live (the store is built before `defineConfig` sees the `schema`
|
|
52
|
+
* option). Not part of the `AccountStore` contract.
|
|
53
|
+
*/
|
|
54
|
+
export declare const BIND_TABLES_LOCATION: unique symbol;
|
|
55
|
+
/** Hands `location` to a store that accepts it (see {@link BIND_TABLES_LOCATION}). */
|
|
56
|
+
export declare function bindTablesLocation(store: unknown, location: AuthkitTablesLocation): void;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** Raised for a `schema.name` the database cannot use. The provider fails the boot with it. */
|
|
2
|
+
export class AuthkitSchemaError extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = 'AuthkitSchemaError';
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
const SCHEMA_NAME = /^[A-Za-z_][A-Za-z0-9_$]*$/;
|
|
9
|
+
/** Throws {@link AuthkitSchemaError} unless `name` is a plain identifier of at most 63 chars. */
|
|
10
|
+
export function validateSchemaName(name, option = 'schema.name') {
|
|
11
|
+
if (name === undefined)
|
|
12
|
+
return undefined;
|
|
13
|
+
if (typeof name !== 'string' || name.length > 63 || !SCHEMA_NAME.test(name)) {
|
|
14
|
+
throw new AuthkitSchemaError(`authkit: ${option} must match /^[A-Za-z_][A-Za-z0-9_$]*$/ and have at most 63 characters (got ${JSON.stringify(name)})`);
|
|
15
|
+
}
|
|
16
|
+
return name;
|
|
17
|
+
}
|
|
18
|
+
/** `schema.table` when a schema is set (knex quotes each part: `"schema"."table"`), else `table`. */
|
|
19
|
+
export function authkitTable(location, table) {
|
|
20
|
+
return location?.schema ? `${location.schema}.${table}` : table;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The query client for AuthKit's tables. `db` is the Lucid manager (`Database`) or a
|
|
24
|
+
* client the caller already holds (a migration's `this.db`, a transaction), which is
|
|
25
|
+
* used as is.
|
|
26
|
+
*/
|
|
27
|
+
export function authkitTablesClient(db, location = {}) {
|
|
28
|
+
if (typeof db?.connection !== 'function')
|
|
29
|
+
return db;
|
|
30
|
+
return location.connection ? db.connection(location.connection) : db.connection();
|
|
31
|
+
}
|
|
32
|
+
const SQLITE_DIALECTS = new Set(['sqlite3', 'better-sqlite3', 'libsql']);
|
|
33
|
+
/** Dialects AuthKit writes and creates schemas on. */
|
|
34
|
+
const SCHEMA_DIALECTS = new Set(['postgres', 'redshift', 'mysql']);
|
|
35
|
+
/** The Lucid dialect name of a client or manager (`postgres`, `mysql`, `better-sqlite3`…). */
|
|
36
|
+
export function dialectOf(client) {
|
|
37
|
+
return client?.dialect?.name;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Throws {@link AuthkitSchemaError} when `schema` is set on a dialect AuthKit cannot use it on:
|
|
41
|
+
* SQLite (no schemas) and any dialect other than Postgres and MySQL. AuthKit never ignores the
|
|
42
|
+
* option nor falls back to unqualified tables.
|
|
43
|
+
*/
|
|
44
|
+
export function assertSchemaSupported(client, schema) {
|
|
45
|
+
if (!schema)
|
|
46
|
+
return;
|
|
47
|
+
const dialect = dialectOf(client);
|
|
48
|
+
if (dialect && SCHEMA_DIALECTS.has(dialect))
|
|
49
|
+
return;
|
|
50
|
+
throw new AuthkitSchemaError(dialect && SQLITE_DIALECTS.has(dialect)
|
|
51
|
+
? `authkit: schema.name "${schema}" is set, but the connection uses ${dialect}, which has no schemas. Remove schema.name or use Postgres or MySQL.`
|
|
52
|
+
: `authkit: schema.name "${schema}" is set, but the connection uses ${dialect ?? 'an unknown dialect'}; schema.name supports Postgres and MySQL. Remove schema.name or use one of them.`);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* `CREATE SCHEMA IF NOT EXISTS` (Postgres) / `CREATE DATABASE IF NOT EXISTS` (MySQL). A role
|
|
56
|
+
* that cannot create it gets an {@link AuthkitSchemaError} naming the schema and the grant.
|
|
57
|
+
*/
|
|
58
|
+
export async function createDbSchema(client, schema) {
|
|
59
|
+
assertSchemaSupported(client, schema);
|
|
60
|
+
const dialect = dialectOf(client);
|
|
61
|
+
const mysql = dialect === 'mysql';
|
|
62
|
+
const sql = mysql
|
|
63
|
+
? `create database if not exists \`${schema}\``
|
|
64
|
+
: `create schema if not exists "${schema}"`;
|
|
65
|
+
try {
|
|
66
|
+
await client.rawQuery(sql);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
const grant = mysql
|
|
70
|
+
? `GRANT CREATE ON *.* (or create it: CREATE DATABASE \`${schema}\`)`
|
|
71
|
+
: `GRANT CREATE ON DATABASE <db> TO <role> (or create it: CREATE SCHEMA "${schema}")`;
|
|
72
|
+
throw new AuthkitSchemaError(`authkit: could not create schema "${schema}" (${error?.message ?? error}). The database role needs ${grant}.`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Hidden hook a store built by `lucidAccountStore` exposes so `defineConfig` can tell it
|
|
77
|
+
* where AuthKit's tables live (the store is built before `defineConfig` sees the `schema`
|
|
78
|
+
* option). Not part of the `AccountStore` contract.
|
|
79
|
+
*/
|
|
80
|
+
export const BIND_TABLES_LOCATION = Symbol.for('authkit.bindTablesLocation');
|
|
81
|
+
/** Hands `location` to a store that accepts it (see {@link BIND_TABLES_LOCATION}). */
|
|
82
|
+
export function bindTablesLocation(store, location) {
|
|
83
|
+
const bind = store?.[BIND_TABLES_LOCATION];
|
|
84
|
+
if (typeof bind === 'function')
|
|
85
|
+
bind(location);
|
|
86
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adonis-agora/authkit-server",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.12",
|
|
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",
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
}
|
|
109
109
|
},
|
|
110
110
|
"dependencies": {
|
|
111
|
-
"@adonis-agora/authkit-core": "1.0.0-rc.
|
|
111
|
+
"@adonis-agora/authkit-core": "1.0.0-rc.2",
|
|
112
112
|
"@simplewebauthn/browser": "14.0.0",
|
|
113
113
|
"@simplewebauthn/server": "14.1.0",
|
|
114
114
|
"jose": "6.2.16",
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"qrcode": "1.5.4"
|
|
120
120
|
},
|
|
121
121
|
"devDependencies": {
|
|
122
|
-
"@adonis-agora/authkit-react": "1.0.0-rc.
|
|
122
|
+
"@adonis-agora/authkit-react": "1.0.0-rc.5",
|
|
123
123
|
"@adonis-agora/durable": "0.60.0",
|
|
124
124
|
"@adonis-agora/telescope": "0.27.1",
|
|
125
125
|
"@adonisjs/ally": "6.3.2",
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
Set up the @adonis-agora/authkit-server OIDC/OAuth2 Authorization Server (Identity
|
|
5
5
|
Provider) in an AdonisJS app. Covers `node ace add @adonis-agora/authkit-server`,
|
|
6
6
|
defineConfig({ issuer, adapter, jwks, ttl, globalRolesClaim, accountStore }) with
|
|
7
|
-
adapters.redis({ connection }) vs adapters.database({ connection? }),
|
|
7
|
+
adapters.redis({ connection }) vs adapters.database({ connection?, schema? }),
|
|
8
8
|
lucidAccountStore(AuthUser), JWKS management ({ source: 'managed' | 'jwks' } or
|
|
9
9
|
'auto', keystore store file/drive/lucid/redis/hashicorp vaults plus
|
|
10
10
|
@adonis-agora/authkit-vault-aws/azure/gcp createKeystoreVault add-ons),
|
|
@@ -83,12 +83,18 @@ export default authServerConfig
|
|
|
83
83
|
|
|
84
84
|
`adapter` is where oidc-provider persists grants/tokens/codes: `adapters.redis({
|
|
85
85
|
connection })` requires `@adonisjs/redis` configured; `adapters.database({
|
|
86
|
-
connection? })` uses Lucid and stores artifacts in `authkit_oidc_payloads`, which
|
|
87
|
-
`ensureAuthkitSchema` creates on boot (with `schema: { autoManage: false }`, call
|
|
88
|
-
|
|
86
|
+
connection?, schema? })` uses Lucid and stores artifacts in `authkit_oidc_payloads`, which
|
|
87
|
+
`ensureAuthkitSchema` creates on boot (with `schema: { autoManage: false }`, call
|
|
88
|
+
`ensureAuthkitSchema(db)` — or `ensureAuthkitSchema(db, { schema })` when `schema.name`
|
|
89
|
+
is set — from a migration). The `schema` defaults to `schema.name` of `defineConfig`.
|
|
90
|
+
The table carries an indexed `account_id` column so per-account
|
|
89
91
|
session lookups and revocation stay in SQL; rows with a NULL `account_id` are still
|
|
90
|
-
found and get classified on read, so no backfill is needed.
|
|
91
|
-
MySQL and SQLite;
|
|
92
|
+
found and get classified on read, so no backfill is needed. Without `schema.name` it
|
|
93
|
+
works on Postgres, MySQL and SQLite; with `schema: { name: 'auth' }` every AuthKit
|
|
94
|
+
table lives in that DB schema (`"auth"."auth_settings"`), the boot creates it
|
|
95
|
+
(`CREATE SCHEMA IF NOT EXISTS`; MySQL: `CREATE DATABASE IF NOT EXISTS`), and SQLite
|
|
96
|
+
fails the boot instead of ignoring the option. Never use `searchPath` for this
|
|
97
|
+
(it leaks across clients behind PgBouncer in transaction mode). Expired rows are pruned every 10 minutes per connection. `accountStore` is the identity contract — `lucidAccountStore(AuthUser)`
|
|
92
98
|
derives `findAccount`/`verifyCredentials` from your user model.
|
|
93
99
|
|
|
94
100
|
Source: `stubs/config/authkit.stub`, `src/define_config.ts` (`AuthServerConfigInput`),
|
|
@@ -112,7 +118,7 @@ blob lives); `{ source: 'jwks', keys }` supplies keys inline.
|
|
|
112
118
|
|
|
113
119
|
The managed keystore's `store` accepts a path shortcut (`store: 'tmp/keystore.json'`)
|
|
114
120
|
or `{ driver: ... }`: built-in vaults are `file`, `drive` (a `@adonisjs/drive` disk),
|
|
115
|
-
`lucid` (table created on first write), `redis`, and `hashicorp`; for cloud secret
|
|
121
|
+
`lucid` (table created on first write; `schema` overrides, default `schema.name`), `redis`, and `hashicorp`; for cloud secret
|
|
116
122
|
managers install the add-ons `@adonis-agora/authkit-vault-aws` / `-azure` / `-gcp`
|
|
117
123
|
and pass their `createKeystoreVault(...)` result as `store` (any object with a
|
|
118
124
|
`read()` method is accepted). With no `store` at all, keys are ephemeral per boot.
|