@adonis-agora/authkit-server 0.66.3 → 0.68.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -0
- package/build/providers/authkit_server_provider.js +11 -1
- package/build/src/accounts/account_store.d.ts +14 -0
- package/build/src/accounts/lucid_account_store.d.ts +22 -22
- package/build/src/accounts/lucid_account_store.js +48 -28
- package/build/src/accounts/lucid_store/organization_models.d.ts +67 -0
- package/build/src/accounts/lucid_store/organization_models.js +113 -0
- package/build/src/accounts/lucid_stores.d.ts +16 -3
- package/build/src/accounts/lucid_stores.js +6 -1
- package/build/src/doctor/checks.d.ts +5 -1
- package/build/src/doctor/checks.js +19 -4
- package/build/src/schema/ensure.d.ts +16 -0
- package/build/src/schema/ensure.js +43 -20
- package/build/stubs/config/authkit_react.stub +3 -12
- package/build/stubs/migrations/create_auth_users_table.stub +4 -4
- package/build/stubs/models/auth_user.stub +3 -3
- package/package.json +2 -1
- package/skills/authkit-interactions/SKILL.md +18 -12
- package/stubs/config/authkit_react.stub +3 -12
- package/stubs/migrations/create_auth_users_table.stub +4 -4
- package/stubs/models/auth_user.stub +3 -3
package/build/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export type { AccountDeletionCapability, AccountImportCapability, AccountSecurit
|
|
|
7
7
|
export { supportsAccountDeletion, supportsAccountImport, supportsAccountSecurity, supportsAccountStatus, supportsEmailVerificationStatus, supportsLoginMethodsPreference, supportsMagicLink, supportsMfa, supportsOrganizations, supportsOtpLogin, supportsPasskeys, supportsProfile, supportsProviderIdentity, } from './src/accounts/account_store.js';
|
|
8
8
|
export type { AccountSecretEncrypter, LucidAccountStoreOptions, } from './src/accounts/lucid_account_store.js';
|
|
9
9
|
export { appKeyEncrypter, lucidAccountStore } from './src/accounts/lucid_account_store.js';
|
|
10
|
+
export { AuthOrganization, AuthOrganizationInvitation, AuthOrganizationMember, defaultOrganizationModels, } from './src/accounts/lucid_store/organization_models.js';
|
|
10
11
|
export type { LucidStoresModels, LucidStoresOptions, LucidStoresResult, } from './src/accounts/lucid_stores.js';
|
|
11
12
|
export { lucidStores } from './src/accounts/lucid_stores.js';
|
|
12
13
|
export type { AuditEvent, AuditEventType, AuditPage, AuditSink, ListAuditParams, StoredAuditEvent, } from './src/audit/audit_sink.js';
|
package/build/index.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
export { configure } from './commands/configure.js';
|
|
6
6
|
export { supportsAccountDeletion, supportsAccountImport, supportsAccountSecurity, supportsAccountStatus, supportsEmailVerificationStatus, supportsLoginMethodsPreference, supportsMagicLink, supportsMfa, supportsOrganizations, supportsOtpLogin, supportsPasskeys, supportsProfile, supportsProviderIdentity, } from './src/accounts/account_store.js';
|
|
7
7
|
export { appKeyEncrypter, lucidAccountStore } from './src/accounts/lucid_account_store.js';
|
|
8
|
+
export { AuthOrganization, AuthOrganizationInvitation, AuthOrganizationMember, defaultOrganizationModels, } from './src/accounts/lucid_store/organization_models.js';
|
|
8
9
|
export { lucidStores } from './src/accounts/lucid_stores.js';
|
|
9
10
|
export { lucidAuditSink } from './src/audit/lucid_audit_sink.js';
|
|
10
11
|
export { adapters, defineConfig, resolveAdmin, resolveAdminApi, resolveAuthMethodsConfig, resolveDynamicRegistration, resolveLogin, resolveNotifications, resolveOrganizations, resolvePasswordless, resolveRateLimit, resolveRegistration, resolveWebauthn, toSeconds, } from './src/define_config.js';
|
|
@@ -218,7 +218,10 @@ export default class AuthkitServerProvider {
|
|
|
218
218
|
if (!db)
|
|
219
219
|
return; // host sem @adonisjs/lucid — nada a gerenciar
|
|
220
220
|
const { ensureAuthkitSchema } = await import('../src/schema/ensure.js');
|
|
221
|
-
const report = await ensureAuthkitSchema(db, {
|
|
221
|
+
const report = await ensureAuthkitSchema(db, {
|
|
222
|
+
connection: config.schema.connection,
|
|
223
|
+
accountTable: config.accountStore?.accountTable,
|
|
224
|
+
});
|
|
222
225
|
const logger = await this.app.container.make('logger').catch(() => null);
|
|
223
226
|
if (report.created.length > 0) {
|
|
224
227
|
logger?.info('authkit: created tables %s', report.created.join(', '));
|
|
@@ -226,6 +229,13 @@ export default class AuthkitServerProvider {
|
|
|
226
229
|
for (const [table, columns] of Object.entries(report.altered)) {
|
|
227
230
|
logger?.info('authkit: added columns to %s: %s', table, columns.join(', '));
|
|
228
231
|
}
|
|
232
|
+
if (!report.loginMethods.ensured) {
|
|
233
|
+
// Sem este aviso o host não tinha como saber: a coluna não foi criada e a
|
|
234
|
+
// preferência de método de login por conta fica no-op em silêncio.
|
|
235
|
+
logger?.warn('authkit: tabela da conta "%s" não existe — a coluna `login_methods` não foi ' +
|
|
236
|
+
'garantida e a preferência de método de login por conta não vai funcionar. ' +
|
|
237
|
+
'Defina `accountStore.accountTable` (ou crie a tabela) se o nome estiver errado.', report.loginMethods.table);
|
|
238
|
+
}
|
|
229
239
|
}
|
|
230
240
|
catch (error) {
|
|
231
241
|
const logger = await this.app.container.make('logger').catch(() => null);
|
|
@@ -627,6 +627,20 @@ export type AccountStore = CoreAccountStore & {
|
|
|
627
627
|
* blocos `Partial<...>` de capacidades probáveis.
|
|
628
628
|
*/
|
|
629
629
|
readonly connectionName?: string;
|
|
630
|
+
/**
|
|
631
|
+
* Nome da tabela da conta (deriva de `Model.table` — e, se o model ainda não
|
|
632
|
+
* bootou, da naming strategy; ver `lucid_account_store.ts`). Mesmo espírito do
|
|
633
|
+
* {@link AccountStore.connectionName}: metadado opcional, não método de
|
|
634
|
+
* capacidade.
|
|
635
|
+
*
|
|
636
|
+
* Existe porque a coluna opcional `login_methods` é garantida no boot pelo
|
|
637
|
+
* `ensureAuthkitSchema`, e ele precisa saber em QUAL tabela ela pertence. Sem
|
|
638
|
+
* este metadado só resta assumir `users`, e aí uma conta cuja tabela tem outro
|
|
639
|
+
* nome (por exemplo `auth_users`, que é o que o scaffold da lib usa) fica com
|
|
640
|
+
* a coluna numa tabela que ninguém consulta — sem erro, sem aviso.
|
|
641
|
+
* Undefined → `users` (back-compat com stores próprios).
|
|
642
|
+
*/
|
|
643
|
+
readonly accountTable?: string;
|
|
630
644
|
} & Partial<MfaCapability & WebauthnCapability & ProviderIdentityCapability & AccountSecurityCapability & AccountStatusCapability & ProfileCapability & MagicLinkCapability & OtpLoginCapability & EmailVerificationStatusCapability & AccountDeletionCapability & AccountImportCapability & OrganizationsCapability & PasswordHistoryCapability & PasswordExpirationCapability & LoginMethodsPreferenceCapability>;
|
|
631
645
|
/** Type guard: o store implementa a capacidade de MFA / TOTP. */
|
|
632
646
|
export declare function supportsMfa(store: AccountStore): store is AccountStore & MfaCapability;
|
|
@@ -136,12 +136,29 @@ export interface LucidAccountStoreOptions {
|
|
|
136
136
|
*/
|
|
137
137
|
pwnedFetch?: FetchLike;
|
|
138
138
|
/**
|
|
139
|
-
* Models Lucid para organizations (multi-tenancy).
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
139
|
+
* Models Lucid para organizations (multi-tenancy).
|
|
140
|
+
*
|
|
141
|
+
* - **Ausente (default)** → usa os models DEFAULT da lib
|
|
142
|
+
* ({@link defaultOrganizationModels}), que já mapeiam as três tabelas
|
|
143
|
+
* lib-owned (`auth_organizations`, `auth_organization_members`,
|
|
144
|
+
* `auth_organization_invitations`). A `OrganizationsCapability` passa a
|
|
145
|
+
* existir por padrão. Isso é seguro porque as rotas do console de conta para
|
|
146
|
+
* orgs JÁ eram montadas por default (`register_auth_host`, bloco `if
|
|
147
|
+
* (mountOrgs)`, com controller capability-probed); antes, sem os models, a
|
|
148
|
+
* tela montada respondia 403; agora ela funciona. Não há tela nova — só uma
|
|
149
|
+
* que deixou de estar quebrada. As tabelas são criadas/evoluídas pelo
|
|
150
|
+
* `ensureAuthkitSchema`, então o mapeamento não é decisão do host.
|
|
151
|
+
* - `true` → idêntico ao default (atalho explícito; é o que apps existentes
|
|
152
|
+
* já usam e continua valendo).
|
|
153
|
+
* - `{ OrgModel, MemberModel, InvitationModel }` → escape hatch, para quem
|
|
154
|
+
* guarda as tabelas de auth numa conexão/schema próprios (os defaults não
|
|
155
|
+
* declaram `static connection`).
|
|
156
|
+
* - `false` → opt-out explícito: `OrganizationsCapability` AUSENTE no store.
|
|
157
|
+
* Use quando a dependência atual é `supportsOrganizations === false` (ex.:
|
|
158
|
+
* store/rotas que assumem a ausência). O `authkit:doctor` avisa se
|
|
159
|
+
* `organizations.enabled: true` e o store não tem a capability.
|
|
143
160
|
*/
|
|
144
|
-
organizationModels?: {
|
|
161
|
+
organizationModels?: true | false | {
|
|
145
162
|
OrgModel: any;
|
|
146
163
|
MemberModel: any;
|
|
147
164
|
InvitationModel: any;
|
|
@@ -153,23 +170,6 @@ export interface LucidAccountStoreOptions {
|
|
|
153
170
|
*/
|
|
154
171
|
emailTokens?: EmailTokensConfigInput;
|
|
155
172
|
}
|
|
156
|
-
/**
|
|
157
|
-
* Implementação default do {@link AccountStore} sobre um model Lucid composto
|
|
158
|
-
* de `withAuthUser()` + `withCredentials()` (+ opcionalmente `withMfa()`). O
|
|
159
|
-
* model carrega `connection`/`table` (app-específico) e, por convenção, uma
|
|
160
|
-
* coluna `fullName` (mapeada de `name`).
|
|
161
|
-
*
|
|
162
|
-
* Composição por CAPACIDADE: o núcleo + MFA são sempre montados; passkeys
|
|
163
|
-
* (WebAuthn) e account linking por provider só são montados quando o model
|
|
164
|
-
* correspondente é fornecido — caso contrário a capacidade fica ABSENTE (os
|
|
165
|
-
* métodos não existem no objeto retornado, em vez de presentes-mas-lançando).
|
|
166
|
-
*
|
|
167
|
-
* @remarks Para capabilities que dependem de tabelas opcionais (ex.:
|
|
168
|
-
* `auth_password_history`), use {@link lucidAccountStoreAsync} que probe o DB
|
|
169
|
-
* e monta o store já com as capabilities detectadas, sem a necessidade de
|
|
170
|
-
* fornecer um model separado. A versão síncrona (`lucidAccountStore`) é mantida
|
|
171
|
-
* por back-compat — capabilities de tabela ficam AUSENTES nela.
|
|
172
|
-
*/
|
|
173
173
|
export declare function lucidAccountStore(Model: any, options?: LucidAccountStoreOptions): AccountStore;
|
|
174
174
|
/**
|
|
175
175
|
* Options estendidas para uso em testes ou quando o DB é injetado diretamente
|
|
@@ -3,6 +3,7 @@ import { PasswordManager } from '../password/password_manager.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';
|
|
6
|
+
import { defaultOrganizationModels } from './lucid_store/organization_models.js';
|
|
6
7
|
import { buildOrganizations } from './lucid_store/organizations.js';
|
|
7
8
|
import { buildPasswordExpiration, buildPasswordHistory } from './lucid_store/password_hygiene.js';
|
|
8
9
|
import { buildProviderIdentity } from './lucid_store/provider_identity.js';
|
|
@@ -103,23 +104,24 @@ export function appKeyEncrypter() {
|
|
|
103
104
|
},
|
|
104
105
|
};
|
|
105
106
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
107
|
+
function resolveAccountTable(Model) {
|
|
108
|
+
try {
|
|
109
|
+
if (typeof Model?.table === 'string' && Model.table.length > 0) {
|
|
110
|
+
return Model.table;
|
|
111
|
+
}
|
|
112
|
+
const naming = Model?.namingStrategy;
|
|
113
|
+
if (naming && typeof naming.tableName === 'function') {
|
|
114
|
+
const resolved = naming.tableName(Model);
|
|
115
|
+
if (typeof resolved === 'string' && resolved.length > 0) {
|
|
116
|
+
return resolved;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// Model ainda não pronto (ou não-Lucid) — metadado é opcional.
|
|
122
|
+
}
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
123
125
|
export function lucidAccountStore(Model, options = {}) {
|
|
124
126
|
const mfaIssuer = options.mfaIssuer ?? 'AuthKit';
|
|
125
127
|
const recoveryCodeCount = options.recoveryCodeCount ?? 8;
|
|
@@ -128,7 +130,15 @@ export function lucidAccountStore(Model, options = {}) {
|
|
|
128
130
|
const encrypter = options.encrypter === false ? undefined : (options.encrypter ?? appKeyEncrypter());
|
|
129
131
|
const ProviderIdentityModel = options.providerIdentityModel;
|
|
130
132
|
const WebauthnCredentialModel = options.webauthnCredentialModel;
|
|
131
|
-
|
|
133
|
+
// Organizations é padrão: ausente OU `true` → models default da lib. O
|
|
134
|
+
// opt-out explícito é `false`. O trio explícito segue como escape hatch.
|
|
135
|
+
const orgModelsOption = options.organizationModels;
|
|
136
|
+
const usesDefaultOrgModels = orgModelsOption === undefined || orgModelsOption === true;
|
|
137
|
+
const OrgModels = orgModelsOption === false
|
|
138
|
+
? undefined
|
|
139
|
+
: usesDefaultOrgModels
|
|
140
|
+
? defaultOrganizationModels
|
|
141
|
+
: orgModelsOption;
|
|
132
142
|
// RP do WebAuthn: usado nas cerimônias. Default do rpName cai no mfaIssuer.
|
|
133
143
|
const webauthn = options.webauthn ?? {
|
|
134
144
|
rpName: mfaIssuer,
|
|
@@ -199,6 +209,10 @@ export function lucidAccountStore(Model, options = {}) {
|
|
|
199
209
|
const store = {
|
|
200
210
|
...buildCore(ctx),
|
|
201
211
|
...buildMfa(ctx),
|
|
212
|
+
// Metadado (não capacidade): a tabela da conta, para o ensure da coluna
|
|
213
|
+
// `login_methods` e o doctor saberem onde ela pertence em vez de assumir
|
|
214
|
+
// `users`. Ver `resolveAccountTable`.
|
|
215
|
+
accountTable: resolveAccountTable(Model),
|
|
202
216
|
...(ProviderIdentityModel ? buildProviderIdentity(ctx, ProviderIdentityModel) : {}),
|
|
203
217
|
...(WebauthnCredentialModel
|
|
204
218
|
? buildWebauthn(ctx, WebauthnCredentialModel, webauthn, ceremonies)
|
|
@@ -216,17 +230,23 @@ export function lucidAccountStore(Model, options = {}) {
|
|
|
216
230
|
// Preferência por usuário de tipos de login: só quando o model tem a coluna
|
|
217
231
|
// `login_methods` (JSONB). Sem a coluna → capacidade ausente (feature no-op).
|
|
218
232
|
...(supportsLoginMethodsColumn(Model) ? buildLoginMethods(ctx) : {}),
|
|
219
|
-
// Organizations (multi-tenancy):
|
|
233
|
+
// Organizations (multi-tenancy): por padrão usa os models default da lib;
|
|
234
|
+
// ausente só com `organizationModels: false` (opt-out explícito).
|
|
220
235
|
...(OrgModels
|
|
221
|
-
?
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
236
|
+
? {
|
|
237
|
+
// Origem dos models, para o `authkit:doctor` reportar sem re-inferir.
|
|
238
|
+
// NÃO faz parte do contrato AccountStore.
|
|
239
|
+
__organizationModelsSource: usesDefaultOrgModels ? 'default' : 'explicit',
|
|
240
|
+
...buildOrganizations({
|
|
241
|
+
OrgModel: OrgModels.OrgModel,
|
|
242
|
+
MemberModel: OrgModels.MemberModel,
|
|
243
|
+
InvitationModel: OrgModels.InvitationModel,
|
|
244
|
+
findAccountEmail: async (accountId) => {
|
|
245
|
+
const row = await Model.find(accountId);
|
|
246
|
+
return row?.email ?? null;
|
|
247
|
+
},
|
|
248
|
+
}),
|
|
249
|
+
}
|
|
230
250
|
: {}),
|
|
231
251
|
// Config de senha resolvida — exposta (não-enumerável) para o authkit:doctor
|
|
232
252
|
// inspecionar policy/checkPwned. NÃO faz parte do contrato AccountStore.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { BaseModel } from '@adonisjs/lucid/orm';
|
|
2
|
+
import type { DateTime } from 'luxon';
|
|
3
|
+
/**
|
|
4
|
+
* Models default das três tabelas de organizations.
|
|
5
|
+
*
|
|
6
|
+
* Estas tabelas são LIB-OWNED: quem as cria e evolui é o `ensureAuthkitSchema`
|
|
7
|
+
* (ver `TABLES` em `src/schema/ensure.ts`). Exigir que cada host reescrevesse
|
|
8
|
+
* este mapeamento à mão rendia duas coisas ruins — boilerplate sem nenhuma
|
|
9
|
+
* decisão do host, e drift silencioso: uma coluna nova nestas tabelas chega
|
|
10
|
+
* pelo `autoManage` sem que o model escrito à mão no host saiba dela, e o
|
|
11
|
+
* sintoma aparece longe da causa.
|
|
12
|
+
*
|
|
13
|
+
* Use com `organizationModels: true` em {@link lucidAccountStore}. O caminho
|
|
14
|
+
* explícito (`{ OrgModel, MemberModel, InvitationModel }`) continua valendo como
|
|
15
|
+
* escape hatch — é para quem guarda as tabelas de auth numa conexão/schema
|
|
16
|
+
* próprios (`static connection = 'auth'`), que estes defaults não declaram.
|
|
17
|
+
*
|
|
18
|
+
* Não crie migration para estas tabelas: elas são criadas/atualizadas pelo
|
|
19
|
+
* `schema.autoManage` do authkit.
|
|
20
|
+
*/
|
|
21
|
+
export declare class AuthOrganization extends BaseModel {
|
|
22
|
+
static table: string;
|
|
23
|
+
/** Os ids são sempre fornecidos pelo builder (randomUUID) — nunca pelo banco. */
|
|
24
|
+
static selfAssignPrimaryKey: boolean;
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
slug: string;
|
|
28
|
+
logoUrl: string | null;
|
|
29
|
+
/**
|
|
30
|
+
* Coluna `json`. O contrato público (`OrgSummary.metadata`) já é
|
|
31
|
+
* `Record<string, unknown> | null`, e é o que o builder grava/lê — declarar o
|
|
32
|
+
* mesmo aqui mantém o model alinhado ao que a lib promete.
|
|
33
|
+
*/
|
|
34
|
+
metadata: Record<string, unknown> | null;
|
|
35
|
+
createdAt: DateTime | null;
|
|
36
|
+
updatedAt: DateTime | null;
|
|
37
|
+
}
|
|
38
|
+
export declare class AuthOrganizationMember extends BaseModel {
|
|
39
|
+
static table: string;
|
|
40
|
+
static selfAssignPrimaryKey: boolean;
|
|
41
|
+
id: string;
|
|
42
|
+
organizationId: string;
|
|
43
|
+
accountId: string;
|
|
44
|
+
role: string;
|
|
45
|
+
createdAt: DateTime | null;
|
|
46
|
+
updatedAt: DateTime | null;
|
|
47
|
+
}
|
|
48
|
+
export declare class AuthOrganizationInvitation extends BaseModel {
|
|
49
|
+
static table: string;
|
|
50
|
+
static selfAssignPrimaryKey: boolean;
|
|
51
|
+
id: string;
|
|
52
|
+
organizationId: string;
|
|
53
|
+
email: string;
|
|
54
|
+
role: string;
|
|
55
|
+
tokenHash: string;
|
|
56
|
+
invitedBy: string;
|
|
57
|
+
expiresAt: DateTime;
|
|
58
|
+
acceptedAt: DateTime | null;
|
|
59
|
+
createdAt: DateTime | null;
|
|
60
|
+
updatedAt: DateTime | null;
|
|
61
|
+
}
|
|
62
|
+
/** O trio que {@link lucidAccountStore} aceita em `organizationModels: true`. */
|
|
63
|
+
export declare const defaultOrganizationModels: {
|
|
64
|
+
readonly OrgModel: typeof AuthOrganization;
|
|
65
|
+
readonly MemberModel: typeof AuthOrganizationMember;
|
|
66
|
+
readonly InvitationModel: typeof AuthOrganizationInvitation;
|
|
67
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { BaseModel, column } from '@adonisjs/lucid/orm';
|
|
8
|
+
/**
|
|
9
|
+
* Models default das três tabelas de organizations.
|
|
10
|
+
*
|
|
11
|
+
* Estas tabelas são LIB-OWNED: quem as cria e evolui é o `ensureAuthkitSchema`
|
|
12
|
+
* (ver `TABLES` em `src/schema/ensure.ts`). Exigir que cada host reescrevesse
|
|
13
|
+
* este mapeamento à mão rendia duas coisas ruins — boilerplate sem nenhuma
|
|
14
|
+
* decisão do host, e drift silencioso: uma coluna nova nestas tabelas chega
|
|
15
|
+
* pelo `autoManage` sem que o model escrito à mão no host saiba dela, e o
|
|
16
|
+
* sintoma aparece longe da causa.
|
|
17
|
+
*
|
|
18
|
+
* Use com `organizationModels: true` em {@link lucidAccountStore}. O caminho
|
|
19
|
+
* explícito (`{ OrgModel, MemberModel, InvitationModel }`) continua valendo como
|
|
20
|
+
* escape hatch — é para quem guarda as tabelas de auth numa conexão/schema
|
|
21
|
+
* próprios (`static connection = 'auth'`), que estes defaults não declaram.
|
|
22
|
+
*
|
|
23
|
+
* Não crie migration para estas tabelas: elas são criadas/atualizadas pelo
|
|
24
|
+
* `schema.autoManage` do authkit.
|
|
25
|
+
*/
|
|
26
|
+
export class AuthOrganization extends BaseModel {
|
|
27
|
+
static table = 'auth_organizations';
|
|
28
|
+
/** Os ids são sempre fornecidos pelo builder (randomUUID) — nunca pelo banco. */
|
|
29
|
+
static selfAssignPrimaryKey = true;
|
|
30
|
+
}
|
|
31
|
+
__decorate([
|
|
32
|
+
column({ isPrimary: true })
|
|
33
|
+
], AuthOrganization.prototype, "id", void 0);
|
|
34
|
+
__decorate([
|
|
35
|
+
column()
|
|
36
|
+
], AuthOrganization.prototype, "name", void 0);
|
|
37
|
+
__decorate([
|
|
38
|
+
column()
|
|
39
|
+
], AuthOrganization.prototype, "slug", void 0);
|
|
40
|
+
__decorate([
|
|
41
|
+
column()
|
|
42
|
+
], AuthOrganization.prototype, "logoUrl", void 0);
|
|
43
|
+
__decorate([
|
|
44
|
+
column()
|
|
45
|
+
], AuthOrganization.prototype, "metadata", void 0);
|
|
46
|
+
__decorate([
|
|
47
|
+
column.dateTime({ autoCreate: true })
|
|
48
|
+
], AuthOrganization.prototype, "createdAt", void 0);
|
|
49
|
+
__decorate([
|
|
50
|
+
column.dateTime({ autoCreate: true, autoUpdate: true })
|
|
51
|
+
], AuthOrganization.prototype, "updatedAt", void 0);
|
|
52
|
+
export class AuthOrganizationMember extends BaseModel {
|
|
53
|
+
static table = 'auth_organization_members';
|
|
54
|
+
static selfAssignPrimaryKey = true;
|
|
55
|
+
}
|
|
56
|
+
__decorate([
|
|
57
|
+
column({ isPrimary: true })
|
|
58
|
+
], AuthOrganizationMember.prototype, "id", void 0);
|
|
59
|
+
__decorate([
|
|
60
|
+
column()
|
|
61
|
+
], AuthOrganizationMember.prototype, "organizationId", void 0);
|
|
62
|
+
__decorate([
|
|
63
|
+
column()
|
|
64
|
+
], AuthOrganizationMember.prototype, "accountId", void 0);
|
|
65
|
+
__decorate([
|
|
66
|
+
column()
|
|
67
|
+
], AuthOrganizationMember.prototype, "role", void 0);
|
|
68
|
+
__decorate([
|
|
69
|
+
column.dateTime({ autoCreate: true })
|
|
70
|
+
], AuthOrganizationMember.prototype, "createdAt", void 0);
|
|
71
|
+
__decorate([
|
|
72
|
+
column.dateTime({ autoCreate: true, autoUpdate: true })
|
|
73
|
+
], AuthOrganizationMember.prototype, "updatedAt", void 0);
|
|
74
|
+
export class AuthOrganizationInvitation extends BaseModel {
|
|
75
|
+
static table = 'auth_organization_invitations';
|
|
76
|
+
static selfAssignPrimaryKey = true;
|
|
77
|
+
}
|
|
78
|
+
__decorate([
|
|
79
|
+
column({ isPrimary: true })
|
|
80
|
+
], AuthOrganizationInvitation.prototype, "id", void 0);
|
|
81
|
+
__decorate([
|
|
82
|
+
column()
|
|
83
|
+
], AuthOrganizationInvitation.prototype, "organizationId", void 0);
|
|
84
|
+
__decorate([
|
|
85
|
+
column()
|
|
86
|
+
], AuthOrganizationInvitation.prototype, "email", void 0);
|
|
87
|
+
__decorate([
|
|
88
|
+
column()
|
|
89
|
+
], AuthOrganizationInvitation.prototype, "role", void 0);
|
|
90
|
+
__decorate([
|
|
91
|
+
column()
|
|
92
|
+
], AuthOrganizationInvitation.prototype, "tokenHash", void 0);
|
|
93
|
+
__decorate([
|
|
94
|
+
column()
|
|
95
|
+
], AuthOrganizationInvitation.prototype, "invitedBy", void 0);
|
|
96
|
+
__decorate([
|
|
97
|
+
column.dateTime()
|
|
98
|
+
], AuthOrganizationInvitation.prototype, "expiresAt", void 0);
|
|
99
|
+
__decorate([
|
|
100
|
+
column.dateTime()
|
|
101
|
+
], AuthOrganizationInvitation.prototype, "acceptedAt", void 0);
|
|
102
|
+
__decorate([
|
|
103
|
+
column.dateTime({ autoCreate: true })
|
|
104
|
+
], AuthOrganizationInvitation.prototype, "createdAt", void 0);
|
|
105
|
+
__decorate([
|
|
106
|
+
column.dateTime({ autoCreate: true, autoUpdate: true })
|
|
107
|
+
], AuthOrganizationInvitation.prototype, "updatedAt", void 0);
|
|
108
|
+
/** O trio que {@link lucidAccountStore} aceita em `organizationModels: true`. */
|
|
109
|
+
export const defaultOrganizationModels = {
|
|
110
|
+
OrgModel: AuthOrganization,
|
|
111
|
+
MemberModel: AuthOrganizationMember,
|
|
112
|
+
InvitationModel: AuthOrganizationInvitation,
|
|
113
|
+
};
|
|
@@ -14,8 +14,16 @@ export interface LucidStoresModels {
|
|
|
14
14
|
providerIdentity?: any;
|
|
15
15
|
/** Model de `withWebauthnCredential()` — habilita passkeys. */
|
|
16
16
|
webauthnCredential?: any;
|
|
17
|
-
/**
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Organizations (multi-tenancy) — habilita a `OrganizationsCapability`.
|
|
19
|
+
*
|
|
20
|
+
* **Ausente → ligada por padrão** (models default da lib; as três tabelas são
|
|
21
|
+
* lib-owned, criadas pelo `ensureAuthkitSchema`). `true` é o atalho explícito
|
|
22
|
+
* para o mesmo caminho; `false` é o opt-out (capability ausente). O trio
|
|
23
|
+
* explícito fica como escape hatch para quem guarda as tabelas de auth numa
|
|
24
|
+
* conexão/schema próprios.
|
|
25
|
+
*/
|
|
26
|
+
organizations?: true | false | {
|
|
19
27
|
OrgModel: any;
|
|
20
28
|
MemberModel: any;
|
|
21
29
|
InvitationModel: any;
|
|
@@ -37,9 +45,14 @@ export type LucidStoresOptions = Omit<LucidAccountStoreOptions, 'providerIdentit
|
|
|
37
45
|
* const { accountStore, patStore, audit } = lucidStores(
|
|
38
46
|
* { account: AuthUser, pat: PersonalAccessToken, audit: AuditLog,
|
|
39
47
|
* providerIdentity: ProviderIdentity, webauthnCredential: WebauthnCredential,
|
|
40
|
-
* organizations:
|
|
48
|
+
* organizations: true },
|
|
41
49
|
* { mfaIssuer: 'educ(a)ção', webauthn }
|
|
42
50
|
* )
|
|
43
51
|
* ```
|
|
52
|
+
*
|
|
53
|
+
* Organizations é ligada por padrão (models default da lib); `organizations:
|
|
54
|
+
* false` desliga. Se as tabelas de auth vivem numa conexão/schema próprios, passe
|
|
55
|
+
* o trio explícito (`{ OrgModel, MemberModel, InvitationModel }`) — os defaults
|
|
56
|
+
* não declaram `static connection`.
|
|
44
57
|
*/
|
|
45
58
|
export declare function lucidStores(models: LucidStoresModels, options?: LucidStoresOptions): LucidStoresResult;
|
|
@@ -10,10 +10,15 @@ import { lucidAccountStore } from './lucid_account_store.js';
|
|
|
10
10
|
* const { accountStore, patStore, audit } = lucidStores(
|
|
11
11
|
* { account: AuthUser, pat: PersonalAccessToken, audit: AuditLog,
|
|
12
12
|
* providerIdentity: ProviderIdentity, webauthnCredential: WebauthnCredential,
|
|
13
|
-
* organizations:
|
|
13
|
+
* organizations: true },
|
|
14
14
|
* { mfaIssuer: 'educ(a)ção', webauthn }
|
|
15
15
|
* )
|
|
16
16
|
* ```
|
|
17
|
+
*
|
|
18
|
+
* Organizations é ligada por padrão (models default da lib); `organizations:
|
|
19
|
+
* false` desliga. Se as tabelas de auth vivem numa conexão/schema próprios, passe
|
|
20
|
+
* o trio explícito (`{ OrgModel, MemberModel, InvitationModel }`) — os defaults
|
|
21
|
+
* não declaram `static connection`.
|
|
17
22
|
*/
|
|
18
23
|
export function lucidStores(models, options = {}) {
|
|
19
24
|
const accountStore = lucidAccountStore(models.account, {
|
|
@@ -119,7 +119,11 @@ export declare function checkAccessTokens(input: DoctorInput): Finding | null;
|
|
|
119
119
|
/**
|
|
120
120
|
* Organizations (multi-tenancy). Informa se a capacidade está disponível
|
|
121
121
|
* (store expõe createOrg) e avisa se `organizations.enabled: true` no config mas
|
|
122
|
-
* a capacidade não está presente no store
|
|
122
|
+
* a capacidade não está presente no store.
|
|
123
|
+
*
|
|
124
|
+
* Com `lucidAccountStore()`, a capability existe POR PADRÃO (models default da
|
|
125
|
+
* lib); o aviso abaixo só é alcançável para store não-lucid que não a implementa
|
|
126
|
+
* ou para um store lucid construído com `organizationModels: false` (opt-out).
|
|
123
127
|
*/
|
|
124
128
|
export declare function checkOrganizations(input: DoctorInput): Finding | null;
|
|
125
129
|
/**
|
|
@@ -488,7 +488,11 @@ export function checkAccessTokens(input) {
|
|
|
488
488
|
/**
|
|
489
489
|
* Organizations (multi-tenancy). Informa se a capacidade está disponível
|
|
490
490
|
* (store expõe createOrg) e avisa se `organizations.enabled: true` no config mas
|
|
491
|
-
* a capacidade não está presente no store
|
|
491
|
+
* a capacidade não está presente no store.
|
|
492
|
+
*
|
|
493
|
+
* Com `lucidAccountStore()`, a capability existe POR PADRÃO (models default da
|
|
494
|
+
* lib); o aviso abaixo só é alcançável para store não-lucid que não a implementa
|
|
495
|
+
* ou para um store lucid construído com `organizationModels: false` (opt-out).
|
|
492
496
|
*/
|
|
493
497
|
export function checkOrganizations(input) {
|
|
494
498
|
const cfg = input.authkitConfig;
|
|
@@ -501,15 +505,26 @@ export function checkOrganizations(input) {
|
|
|
501
505
|
return {
|
|
502
506
|
level: 'warn',
|
|
503
507
|
message: 'organizations.enabled: true, but the accountStore has no OrganizationsCapability — ' +
|
|
504
|
-
'
|
|
505
|
-
'
|
|
508
|
+
'with `lucidAccountStore()` it is present by default; if you passed ' +
|
|
509
|
+
'`organizationModels: false`, drop the opt-out (or pass the explicit ' +
|
|
510
|
+
'`{ OrgModel, MemberModel, InvitationModel }` trio when the auth tables live on their ' +
|
|
511
|
+
'own connection/schema). Expected tables: auth_organizations, ' +
|
|
512
|
+
'auth_organization_members, auth_organization_invitations.',
|
|
506
513
|
};
|
|
507
514
|
}
|
|
508
515
|
if (storeSupports) {
|
|
509
516
|
const roles = cfg.organizations?.roles ?? ['owner', 'admin', 'member'];
|
|
517
|
+
// Origem dos models: default da lib (organizationModels ausente/true) ou
|
|
518
|
+
// trio explícito do host. Ausente em store não-lucid → wording genérica.
|
|
519
|
+
const source = store.__organizationModelsSource;
|
|
520
|
+
const models = source === 'default'
|
|
521
|
+
? 'lib defaults (organizationModels omitted or true)'
|
|
522
|
+
: source === 'explicit'
|
|
523
|
+
? 'host-provided explicit trio'
|
|
524
|
+
: 'lib defaults or host-provided models';
|
|
510
525
|
return {
|
|
511
526
|
level: 'ok',
|
|
512
|
-
message: `organizations capability present (roles: ${roles.join(', ')}).`,
|
|
527
|
+
message: `organizations capability present (models: ${models}; roles: ${roles.join(', ')}).`,
|
|
513
528
|
};
|
|
514
529
|
}
|
|
515
530
|
// Auto mode (enabled === undefined) and store doesn't support — silently ok (opt-in).
|
|
@@ -26,12 +26,28 @@
|
|
|
26
26
|
export interface EnsureSchemaOptions {
|
|
27
27
|
/** Conexão Lucid a usar. Default: conexão primária. */
|
|
28
28
|
connection?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Tabela da conta, onde a coluna opcional `login_methods` pertence. Vem do
|
|
31
|
+
* metadado `accountStore.accountTable` (o provider repassa no boot). Undefined
|
|
32
|
+
* → `users`, que era o único valor possível antes deste metadado existir.
|
|
33
|
+
*/
|
|
34
|
+
accountTable?: string;
|
|
29
35
|
}
|
|
30
36
|
export interface EnsureSchemaReport {
|
|
31
37
|
/** Tabelas criadas do zero nesta execução. */
|
|
32
38
|
created: string[];
|
|
33
39
|
/** Colunas adicionadas em tabelas já existentes: tabela → colunas. */
|
|
34
40
|
altered: Record<string, string[]>;
|
|
41
|
+
/**
|
|
42
|
+
* Desfecho da coluna `login_methods`. `ensured: false` significa que a tabela
|
|
43
|
+
* da conta não existe no banco, então NADA foi feito — e é o caso que
|
|
44
|
+
* silenciava o problema: o host precisa saber que a preferência de método de
|
|
45
|
+
* login não vai funcionar até a tabela existir.
|
|
46
|
+
*/
|
|
47
|
+
loginMethods: {
|
|
48
|
+
table: string;
|
|
49
|
+
ensured: boolean;
|
|
50
|
+
};
|
|
35
51
|
}
|
|
36
52
|
/**
|
|
37
53
|
* Garante que as tabelas do authkit existem e têm todas as colunas que esta
|
|
@@ -234,7 +234,11 @@ async function columnExists(conn, table, column) {
|
|
|
234
234
|
*/
|
|
235
235
|
export async function ensureAuthkitSchema(db, options = {}) {
|
|
236
236
|
const conn = options.connection ? db.connection(options.connection) : db.connection();
|
|
237
|
-
const report = {
|
|
237
|
+
const report = {
|
|
238
|
+
created: [],
|
|
239
|
+
altered: {},
|
|
240
|
+
loginMethods: { table: options.accountTable ?? 'users', ensured: false },
|
|
241
|
+
};
|
|
238
242
|
for (const def of TABLES) {
|
|
239
243
|
if (!(await tableExists(conn, def.name))) {
|
|
240
244
|
try {
|
|
@@ -260,29 +264,48 @@ export async function ensureAuthkitSchema(db, options = {}) {
|
|
|
260
264
|
report.altered[def.name].push(column);
|
|
261
265
|
}
|
|
262
266
|
}
|
|
263
|
-
// Coluna `login_methods`
|
|
264
|
-
// TABLES porque o nome/shape são decisão do app), mas a
|
|
265
|
-
// (LoginMethodsPreferenceCapability). Alguns deploys de host só migram
|
|
266
|
-
// do domínio, não o schema
|
|
267
|
-
// nunca rodar, e isso derrubava TODO login/callback OIDC com
|
|
268
|
-
// does not exist" no instante em que o model passou a
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
267
|
+
// Coluna `login_methods` na tabela da CONTA — a tabela é HOST-owned (não está
|
|
268
|
+
// em TABLES porque o nome/shape são decisão do app), mas a LIB consome a
|
|
269
|
+
// coluna (LoginMethodsPreferenceCapability). Alguns deploys de host só migram
|
|
270
|
+
// o schema do domínio, não o schema de auth — então a migration manual da
|
|
271
|
+
// coluna pode nunca rodar, e isso derrubava TODO login/callback OIDC com
|
|
272
|
+
// "column ...login_methods does not exist" no instante em que o model passou a
|
|
273
|
+
// declará-la.
|
|
274
|
+
//
|
|
275
|
+
// Aqui garantimos: SE a tabela da conta existe E a coluna falta, adicionamos.
|
|
276
|
+
// Nunca criamos a tabela (host-owned). Aditivo + idempotente — roda em todo boot.
|
|
277
|
+
//
|
|
278
|
+
// O nome da tabela vem do store (`accountStore.accountTable`), NÃO de um
|
|
279
|
+
// `users` fixo: com a conta em `auth_users` (o nome que o próprio scaffold da
|
|
280
|
+
// lib usa), o `users` fixo acertava uma tabela qualquer de mesmo nome e a
|
|
281
|
+
// coluna ia parar no lugar errado sem erro nenhum.
|
|
282
|
+
const accountTable = options.accountTable ?? 'users';
|
|
283
|
+
if (await tableExists(conn, accountTable)) {
|
|
284
|
+
try {
|
|
285
|
+
if (!(await columnExists(conn, accountTable, 'login_methods'))) {
|
|
286
|
+
await conn.schema.alterTable(accountTable, (t) => {
|
|
275
287
|
t.jsonb('login_methods').nullable();
|
|
276
288
|
});
|
|
277
|
-
report.altered
|
|
289
|
+
report.altered[accountTable] = ['login_methods'];
|
|
278
290
|
}
|
|
291
|
+
report.loginMethods.ensured = true;
|
|
279
292
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
293
|
+
catch (error) {
|
|
294
|
+
/**
|
|
295
|
+
* Corrida entre instâncias subindo juntas: se a coluna já existe agora,
|
|
296
|
+
* outra ganhou o ALTER — segue o jogo. Qualquer outra falha PROPAGA.
|
|
297
|
+
*
|
|
298
|
+
* O re-probe tem de ser da COLUNA, não da tabela: checar a tabela fazia um
|
|
299
|
+
* ALTER que falhou virar `ensured: true`, ou seja, o report dizia sucesso e
|
|
300
|
+
* o boot não avisava nada — o silêncio que este bloco existe para acabar.
|
|
301
|
+
* (Mesma semântica do probe de `createTable` logo acima.)
|
|
302
|
+
*/
|
|
303
|
+
if (await columnExists(conn, accountTable, 'login_methods')) {
|
|
304
|
+
report.loginMethods.ensured = true;
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
throw error;
|
|
308
|
+
}
|
|
286
309
|
}
|
|
287
310
|
}
|
|
288
311
|
return report;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
}}}
|
|
4
4
|
import env from '#start/env'
|
|
5
5
|
import AuthUser from '#models/auth_user'
|
|
6
|
-
import { defineConfig, adapters, inertiaRenderer } from '@adonis-agora/authkit-server'
|
|
6
|
+
import { defineConfig, adapters, lucidAccountStore, inertiaRenderer } from '@adonis-agora/authkit-server'
|
|
7
7
|
|
|
8
8
|
const authServerConfig = defineConfig({
|
|
9
9
|
issuer: env.get('AUTHKIT_ISSUER'),
|
|
@@ -14,18 +14,9 @@ const authServerConfig = defineConfig({
|
|
|
14
14
|
],
|
|
15
15
|
ttl: { accessToken: '15m', refreshToken: '30d' },
|
|
16
16
|
globalRolesClaim: 'roles',
|
|
17
|
-
|
|
18
|
-
const user = await AuthUser.find(sub)
|
|
19
|
-
if (!user) return null
|
|
20
|
-
return { id: user.id, email: user.email, globalRoles: user.globalRoles }
|
|
21
|
-
},
|
|
22
|
-
verifyCredentials: async (email, password) => {
|
|
23
|
-
const user = await AuthUser.query().where('email', email).first()
|
|
24
|
-
if (!user || !(await user.verifyPassword(password))) return null
|
|
25
|
-
return { id: user.id }
|
|
26
|
-
},
|
|
17
|
+
accountStore: lucidAccountStore(AuthUser),
|
|
27
18
|
/**
|
|
28
|
-
* Renderer Inertia/React: apenas as views listadas em
|
|
19
|
+
* Renderer Inertia/React: apenas as views listadas em "views" vão ao Inertia;
|
|
29
20
|
* as demais (incluindo todas as telas admin/*) recaem silenciosamente no
|
|
30
21
|
* renderer Edge built-in da lib — evitando SSR crash por página inexistente.
|
|
31
22
|
*
|
|
@@ -11,10 +11,10 @@ export default class extends BaseSchema {
|
|
|
11
11
|
async up() {
|
|
12
12
|
this.schema.createTable(this.tableName, (table) => {
|
|
13
13
|
/**
|
|
14
|
-
* String, NÃO increments(): o hook
|
|
15
|
-
* (
|
|
14
|
+
* String, NÃO increments(): o hook @beforeCreate do model scaffoldado
|
|
15
|
+
* (models/auth_user.stub) atribui um randomUUID() antes do insert.
|
|
16
16
|
* Uma coluna auto-increment aceitaria o INSERT mesmo assim, mas o valor
|
|
17
|
-
* do UUID seria descartado e o
|
|
17
|
+
* do UUID seria descartado e o id do model voltaria como o rowid
|
|
18
18
|
* interno do banco — a conta ficaria inalcançável pelo id real na
|
|
19
19
|
* próxima request. Ver packages/authkit-server/stubs/models/auth_user.stub.
|
|
20
20
|
*/
|
|
@@ -34,7 +34,7 @@ export default class extends BaseSchema {
|
|
|
34
34
|
|
|
35
35
|
// Coluna própria do model scaffoldado (não vem de nenhum mixin): a tela
|
|
36
36
|
// de signup embutida sempre coleta "Nome" e o Lucid store passa esse
|
|
37
|
-
// valor direto para
|
|
37
|
+
// valor direto para AuthUser.create() — sem esta coluna o primeiro
|
|
38
38
|
// signup quebra com "Cannot define 'fullName' on 'AuthUser' model,
|
|
39
39
|
// since it is not defined as a model property".
|
|
40
40
|
table.string('full_name').nullable()
|
|
@@ -20,7 +20,7 @@ import { withAuthUser, withCredentials } from '@adonis-agora/authkit-server'
|
|
|
20
20
|
export default class AuthUser extends compose(BaseModel, withAuthUser(), withCredentials()) {
|
|
21
21
|
/**
|
|
22
22
|
* Sem isto, o Lucid ignora o id atribuído pelo hook abaixo e sobrescreve
|
|
23
|
-
*
|
|
23
|
+
* id com o retorno bruto do INSERT (o rowid interno do SQLite/Postgres)
|
|
24
24
|
* assim que a linha é salva — mesmo resultado prático de não ter o hook: a
|
|
25
25
|
* conta some do próprio id que acabou de criar.
|
|
26
26
|
*/
|
|
@@ -31,7 +31,7 @@ export default class AuthUser extends compose(BaseModel, withAuthUser(), withCre
|
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Nem withAuthUser() nem withCredentials() geram o id — sem este hook o
|
|
34
|
-
* Lucid insere NULL nesta coluna string e o
|
|
34
|
+
* Lucid insere NULL nesta coluna string e o id do model volta como o
|
|
35
35
|
* rowid interno do banco, tornando a conta inalcançável pelo id real na
|
|
36
36
|
* próxima request.
|
|
37
37
|
*/
|
|
@@ -42,7 +42,7 @@ export default class AuthUser extends compose(BaseModel, withAuthUser(), withCre
|
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* A tela de signup embutida sempre coleta um campo "Nome", e o Lucid store
|
|
45
|
-
* passa esse valor direto para
|
|
45
|
+
* passa esse valor direto para AuthUser.create() — sem esta coluna o
|
|
46
46
|
* primeiro signup quebra com "Cannot define 'fullName' on 'AuthUser'
|
|
47
47
|
* model, since it is not defined as a model property".
|
|
48
48
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adonis-agora/authkit-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.68.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",
|
|
@@ -153,6 +153,7 @@
|
|
|
153
153
|
"react-dom": "19.2.8",
|
|
154
154
|
"react-error-boundary": "6.1.5",
|
|
155
155
|
"recharts": "3.10.1",
|
|
156
|
+
"tempura": "^0.4.1",
|
|
156
157
|
"typescript": "7.0.2",
|
|
157
158
|
"vite": "8.2.2"
|
|
158
159
|
},
|
|
@@ -6,7 +6,7 @@ description: >-
|
|
|
6
6
|
routes every IdP app must register (show/login/consent on AuthInteractionController),
|
|
7
7
|
`node ace configure --ui=edge|react|headless` presets, the shell-controller +
|
|
8
8
|
service.interactions split (details(ctx), login(ctx,{email,password}), consent(ctx)),
|
|
9
|
-
overriding verifyCredentials in config/authkit.ts, renderers edgeRenderer/inertiaRenderer,
|
|
9
|
+
overriding accountStore.verifyCredentials in config/authkit.ts, renderers edgeRenderer/inertiaRenderer,
|
|
10
10
|
and end-to-end testing with @adonis-agora/authkit-testing (createTestIdentity,
|
|
11
11
|
mintTestIdToken, serveJwks, fakeAuthenticator). Use when the authorization flow 404s at
|
|
12
12
|
the login screen, wiring custom login UI, plugging an external user base, or testing
|
|
@@ -81,24 +81,30 @@ the shell only translates between HTTP and those calls.
|
|
|
81
81
|
Source: `packages/authkit-server/README.md` § UI de login/consent ("o controller
|
|
82
82
|
ejetado é casca: a lógica vive em `service.interactions`").
|
|
83
83
|
|
|
84
|
-
### Pattern 2 — plug your user base via `verifyCredentials`
|
|
84
|
+
### Pattern 2 — plug your user base via `accountStore.verifyCredentials`
|
|
85
85
|
|
|
86
|
-
`verifyCredentials`
|
|
87
|
-
`service.interactions.login` calls it. The default
|
|
88
|
-
|
|
86
|
+
`verifyCredentials` on the configured `accountStore` decides whether credentials
|
|
87
|
+
are valid; `service.interactions.login` calls it. The default
|
|
88
|
+
`lucidAccountStore(AuthUser)` queries the `AuthUser` model by email and uses the
|
|
89
|
+
`withCredentials` mixin — override the store method to authenticate against
|
|
90
|
+
anything else (`findAccount`/`verifyCredentials` are NOT top-level `defineConfig`
|
|
91
|
+
keys; `AuthServerConfigInput` accepts only `accountStore`, from which the lib
|
|
92
|
+
derives both):
|
|
89
93
|
|
|
90
94
|
```ts
|
|
91
95
|
// config/authkit.ts
|
|
92
96
|
defineConfig({
|
|
93
97
|
issuer: env.get('AUTHKIT_ISSUER'),
|
|
94
98
|
adapter: adapters.redis({ connection: 'main' }),
|
|
95
|
-
accountStore:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
accountStore: {
|
|
100
|
+
...lucidAccountStore(AuthUser),
|
|
101
|
+
verifyCredentials: async (email, password) => {
|
|
102
|
+
// Return the account on success; throw/falsy paths fail the login.
|
|
103
|
+
const account = await AuthUser.query().where('email', email).first()
|
|
104
|
+
if (!account) throw new Error('Invalid credentials')
|
|
105
|
+
await verifyPassword(account.passwordHash, password)
|
|
106
|
+
return account
|
|
107
|
+
},
|
|
102
108
|
},
|
|
103
109
|
})
|
|
104
110
|
```
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
}}}
|
|
4
4
|
import env from '#start/env'
|
|
5
5
|
import AuthUser from '#models/auth_user'
|
|
6
|
-
import { defineConfig, adapters, inertiaRenderer } from '@adonis-agora/authkit-server'
|
|
6
|
+
import { defineConfig, adapters, lucidAccountStore, inertiaRenderer } from '@adonis-agora/authkit-server'
|
|
7
7
|
|
|
8
8
|
const authServerConfig = defineConfig({
|
|
9
9
|
issuer: env.get('AUTHKIT_ISSUER'),
|
|
@@ -14,18 +14,9 @@ const authServerConfig = defineConfig({
|
|
|
14
14
|
],
|
|
15
15
|
ttl: { accessToken: '15m', refreshToken: '30d' },
|
|
16
16
|
globalRolesClaim: 'roles',
|
|
17
|
-
|
|
18
|
-
const user = await AuthUser.find(sub)
|
|
19
|
-
if (!user) return null
|
|
20
|
-
return { id: user.id, email: user.email, globalRoles: user.globalRoles }
|
|
21
|
-
},
|
|
22
|
-
verifyCredentials: async (email, password) => {
|
|
23
|
-
const user = await AuthUser.query().where('email', email).first()
|
|
24
|
-
if (!user || !(await user.verifyPassword(password))) return null
|
|
25
|
-
return { id: user.id }
|
|
26
|
-
},
|
|
17
|
+
accountStore: lucidAccountStore(AuthUser),
|
|
27
18
|
/**
|
|
28
|
-
* Renderer Inertia/React: apenas as views listadas em
|
|
19
|
+
* Renderer Inertia/React: apenas as views listadas em "views" vão ao Inertia;
|
|
29
20
|
* as demais (incluindo todas as telas admin/*) recaem silenciosamente no
|
|
30
21
|
* renderer Edge built-in da lib — evitando SSR crash por página inexistente.
|
|
31
22
|
*
|
|
@@ -11,10 +11,10 @@ export default class extends BaseSchema {
|
|
|
11
11
|
async up() {
|
|
12
12
|
this.schema.createTable(this.tableName, (table) => {
|
|
13
13
|
/**
|
|
14
|
-
* String, NÃO increments(): o hook
|
|
15
|
-
* (
|
|
14
|
+
* String, NÃO increments(): o hook @beforeCreate do model scaffoldado
|
|
15
|
+
* (models/auth_user.stub) atribui um randomUUID() antes do insert.
|
|
16
16
|
* Uma coluna auto-increment aceitaria o INSERT mesmo assim, mas o valor
|
|
17
|
-
* do UUID seria descartado e o
|
|
17
|
+
* do UUID seria descartado e o id do model voltaria como o rowid
|
|
18
18
|
* interno do banco — a conta ficaria inalcançável pelo id real na
|
|
19
19
|
* próxima request. Ver packages/authkit-server/stubs/models/auth_user.stub.
|
|
20
20
|
*/
|
|
@@ -34,7 +34,7 @@ export default class extends BaseSchema {
|
|
|
34
34
|
|
|
35
35
|
// Coluna própria do model scaffoldado (não vem de nenhum mixin): a tela
|
|
36
36
|
// de signup embutida sempre coleta "Nome" e o Lucid store passa esse
|
|
37
|
-
// valor direto para
|
|
37
|
+
// valor direto para AuthUser.create() — sem esta coluna o primeiro
|
|
38
38
|
// signup quebra com "Cannot define 'fullName' on 'AuthUser' model,
|
|
39
39
|
// since it is not defined as a model property".
|
|
40
40
|
table.string('full_name').nullable()
|
|
@@ -20,7 +20,7 @@ import { withAuthUser, withCredentials } from '@adonis-agora/authkit-server'
|
|
|
20
20
|
export default class AuthUser extends compose(BaseModel, withAuthUser(), withCredentials()) {
|
|
21
21
|
/**
|
|
22
22
|
* Sem isto, o Lucid ignora o id atribuído pelo hook abaixo e sobrescreve
|
|
23
|
-
*
|
|
23
|
+
* id com o retorno bruto do INSERT (o rowid interno do SQLite/Postgres)
|
|
24
24
|
* assim que a linha é salva — mesmo resultado prático de não ter o hook: a
|
|
25
25
|
* conta some do próprio id que acabou de criar.
|
|
26
26
|
*/
|
|
@@ -31,7 +31,7 @@ export default class AuthUser extends compose(BaseModel, withAuthUser(), withCre
|
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Nem withAuthUser() nem withCredentials() geram o id — sem este hook o
|
|
34
|
-
* Lucid insere NULL nesta coluna string e o
|
|
34
|
+
* Lucid insere NULL nesta coluna string e o id do model volta como o
|
|
35
35
|
* rowid interno do banco, tornando a conta inalcançável pelo id real na
|
|
36
36
|
* próxima request.
|
|
37
37
|
*/
|
|
@@ -42,7 +42,7 @@ export default class AuthUser extends compose(BaseModel, withAuthUser(), withCre
|
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* A tela de signup embutida sempre coleta um campo "Nome", e o Lucid store
|
|
45
|
-
* passa esse valor direto para
|
|
45
|
+
* passa esse valor direto para AuthUser.create() — sem esta coluna o
|
|
46
46
|
* primeiro signup quebra com "Cannot define 'fullName' on 'AuthUser'
|
|
47
47
|
* model, since it is not defined as a model property".
|
|
48
48
|
*/
|