@adonis-agora/authkit-server 0.65.1 → 0.65.3

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.
@@ -27,6 +27,18 @@ export async function configure(command) {
27
27
  // o allowlist `views` já preenchido — evitando SSR crash por páginas inexistentes.
28
28
  const configStub = preset === 'react' ? 'config/authkit_react.stub' : 'config/authkit.stub';
29
29
  await codemods.makeUsingStub(stubsRoot, configStub, {});
30
+ // A tabela do model scaffoldado logo abaixo (`auth_users`) não é gerenciada
31
+ // por `ensureAuthkitSchema()` — ela é HOST-owned de propósito (nome/shape
32
+ // são decisão do host), então sem esta migration o primeiro signup/login
33
+ // quebra com "no such table: auth_users" (ou equivalente Postgres). Ver
34
+ // packages/authkit-server/stubs/migrations/create_auth_users_table.stub.
35
+ await codemods.makeUsingStub(stubsRoot, 'migrations/create_auth_users_table.stub', {
36
+ entity: command.app.generators.createEntity('auth_users'),
37
+ migration: {
38
+ folder: 'database/migrations',
39
+ fileName: `${Date.now()}_create_auth_users_table.ts`,
40
+ },
41
+ });
30
42
  await codemods.makeUsingStub(stubsRoot, 'models/auth_user.stub', {});
31
43
  for (const path of uiStubPaths(preset)) {
32
44
  await codemods.makeUsingStub(stubsRoot, path, {});
@@ -5,7 +5,7 @@ import { type AdapterFactory, adapters, type OidcAdapterClass } from './adapters
5
5
  import type { AuditSink } from './audit/audit_sink.js';
6
6
  import { type EventsConfigInput } from './events/dispatcher.js';
7
7
  import { type BotProtectionConfigInput, type ResolvedBotProtectionConfig } from './host/bot_protection.js';
8
- import type { BrandingConfig } from './host/branding.js';
8
+ import { type BrandingConfig } from './host/branding.js';
9
9
  import { type PolicyRouteOption } from './host/config_locks.js';
10
10
  import type { ResolveGeo } from './host/geo.js';
11
11
  import { type AuthMessages, type I18nConfig } from './host/i18n.js';
@@ -1113,7 +1113,8 @@ export interface ResolvedServerConfig {
1113
1113
  render?: AuthHostRenderer;
1114
1114
  /** Recuperação de sessão de interaction perdida resolvida (default `{ mode: 'screen' }`). */
1115
1115
  interactionRecovery: ResolvedInteractionRecoveryConfig;
1116
- branding?: BrandingConfig;
1116
+ /** Branding resolvido: sempre presente (default neutro quando o host não declara `branding`). */
1117
+ branding: BrandingConfig;
1117
1118
  /**
1118
1119
  * Allowlist resolvida de clients first-party, ou `undefined` quando o host não
1119
1120
  * declarou nenhuma — caso em que todo client registrado é first-party. Ver
@@ -2,6 +2,7 @@ import { configProvider } from '@adonisjs/core';
2
2
  import { adapters } from './adapters/factory.js';
3
3
  import { composeAuditSink, resolveEvents } from './events/dispatcher.js';
4
4
  import { resolveBotProtection, } from './host/bot_protection.js';
5
+ import { resolveBranding } from './host/branding.js';
5
6
  import { deriveLockedRouteOptions, deriveLockedSettingKeys, } from './host/config_locks.js';
6
7
  import { resolveMessages } from './host/i18n.js';
7
8
  import { resolveOtpLoginConfig, } from './host/otp_login.js';
@@ -365,7 +366,7 @@ export function defineConfig(config) {
365
366
  mode: config.interactionRecovery?.mode ?? 'screen',
366
367
  redirectTo: config.interactionRecovery?.redirectTo,
367
368
  },
368
- branding: config.branding,
369
+ branding: resolveBranding(config.branding),
369
370
  // `firstPartyClients` manda; `branding.firstParty` é o fallback de
370
371
  // back-compat. Nenhum dos dois declarado → `undefined` = sem allowlist.
371
372
  firstPartyClients: config.firstPartyClients ?? config.branding?.firstParty,
@@ -251,7 +251,7 @@ export function checkShield(input) {
251
251
  const mountPath = input.authkitConfig?.mountPath ?? '/oidc';
252
252
  return {
253
253
  level: 'warn',
254
- message: `Make sure the IdP POST routes under "${mountPath}" are in the shield CSRF exceptions (e.g. the /token endpoint), otherwise server-to-server calls fail.`,
254
+ message: `Make sure the IdP POST routes under "${mountPath}" are in the shield CSRF exceptions (e.g. the /token endpoint), otherwise server-to-server calls fail. Use the exported authkitCsrfExceptions(url, { mountPath: '${mountPath}' }) helper in config/shield.ts's exceptRoutes instead of hand-writing the route-pattern check.`,
255
255
  };
256
256
  }
257
257
  /** ally só é necessário quando social está configurado. */
@@ -15,6 +15,13 @@ export interface BrandingConfig {
15
15
  firstParty: string[];
16
16
  audienceLabels?: Record<string, string>;
17
17
  }
18
+ /**
19
+ * Resolve a config de branding: quando o host não declara `branding`, cai no
20
+ * {@link DEFAULT_BRANDING} acima (tema neutro, sem clients customizados) em
21
+ * vez de deixar `cfg.branding` como `undefined` — o que fazia a MENOR config
22
+ * documentada (sem branding) quebrar a primeira tela de login/consent/signup.
23
+ */
24
+ export declare function resolveBranding(input: BrandingConfig | undefined): BrandingConfig;
18
25
  export declare function isFirstParty(cfg: BrandingConfig, clientId: string | undefined): boolean;
19
26
  /**
20
27
  * O client é FIRST-PARTY, ou seja, recebe as claims de autorização
@@ -1,3 +1,34 @@
1
+ /**
2
+ * Default `BrandingConfig` usado quando o host não declara `branding` no
3
+ * `defineConfig`. `branding` é tipado opcional e todo doc (getting-started,
4
+ * quickstart, reference) trata como tema puro, dispensável — mas o
5
+ * interaction controller (e alguns outros módulos do host-kit) lê
6
+ * `cfg.branding.clients`/`cfg.branding.default` incondicionalmente para
7
+ * montar a tela de login/consent/signup. Sem este default, o primeiro hit em
8
+ * QUALQUER tela built-in num host sem `branding` estourava
9
+ * `TypeError: Cannot read properties of undefined (reading 'clients')`.
10
+ * Ver `resolveBranding`, chamado em `define_config.ts`.
11
+ */
12
+ const DEFAULT_BRANDING = {
13
+ company: 'AuthKit',
14
+ clients: {},
15
+ default: {
16
+ appName: 'AuthKit',
17
+ accent: '#4f46e5',
18
+ accentSoft: '#e0e7ff',
19
+ tagline: '',
20
+ },
21
+ firstParty: [],
22
+ };
23
+ /**
24
+ * Resolve a config de branding: quando o host não declara `branding`, cai no
25
+ * {@link DEFAULT_BRANDING} acima (tema neutro, sem clients customizados) em
26
+ * vez de deixar `cfg.branding` como `undefined` — o que fazia a MENOR config
27
+ * documentada (sem branding) quebrar a primeira tela de login/consent/signup.
28
+ */
29
+ export function resolveBranding(input) {
30
+ return input ?? DEFAULT_BRANDING;
31
+ }
1
32
  export function isFirstParty(cfg, clientId) {
2
33
  return !!clientId && cfg.firstParty.includes(clientId);
3
34
  }
@@ -0,0 +1,47 @@
1
+ {{{
2
+ exports({
3
+ to: app.makePath(migration.folder, entity.path, migration.fileName)
4
+ })
5
+ }}}
6
+ import { BaseSchema } from '@adonisjs/lucid/schema'
7
+
8
+ export default class extends BaseSchema {
9
+ protected tableName = 'auth_users'
10
+
11
+ async up() {
12
+ this.schema.createTable(this.tableName, (table) => {
13
+ /**
14
+ * String, NÃO increments(): o hook `@beforeCreate` do model scaffoldado
15
+ * (`models/auth_user.stub`) atribui um `randomUUID()` antes do insert.
16
+ * Uma coluna auto-increment aceitaria o INSERT mesmo assim, mas o valor
17
+ * do UUID seria descartado e o `id` do model voltaria como o rowid
18
+ * interno do banco — a conta ficaria inalcançável pelo id real na
19
+ * próxima request. Ver packages/authkit-server/stubs/models/auth_user.stub.
20
+ */
21
+ table.string('id').notNullable().primary()
22
+
23
+ // withAuthUser() — packages/authkit-server/src/mixins/with_auth_user.ts
24
+ table.string('email').notNullable().unique()
25
+ table.string('password').notNullable()
26
+ // Serializado como JSON string ("[]" quando vazio) pelo mixin.
27
+ table.json('global_roles').notNullable().defaultTo('[]')
28
+
29
+ // withCredentials() — packages/authkit-server/src/mixins/with_credentials.ts
30
+ table.timestamp('email_verified_at', { useTz: true }).nullable()
31
+ table.string('email_verification_token').nullable()
32
+ table.string('password_reset_token').nullable()
33
+ table.timestamp('password_reset_expires_at', { useTz: true }).nullable()
34
+
35
+ // Coluna própria do model scaffoldado (não vem de nenhum mixin): a tela
36
+ // de signup embutida sempre coleta "Nome" e o Lucid store passa esse
37
+ // valor direto para `AuthUser.create()` — sem esta coluna o primeiro
38
+ // signup quebra com "Cannot define 'fullName' on 'AuthUser' model,
39
+ // since it is not defined as a model property".
40
+ table.string('full_name').nullable()
41
+ })
42
+ }
43
+
44
+ async down() {
45
+ this.schema.dropTable(this.tableName)
46
+ }
47
+ }
@@ -1,7 +1,8 @@
1
1
  {{{
2
2
  exports({ to: app.makePath('app/models/auth_user.ts') })
3
3
  }}}
4
- import { BaseModel, column } from '@adonisjs/lucid/orm'
4
+ import { randomUUID } from 'node:crypto'
5
+ import { BaseModel, beforeCreate, column } from '@adonisjs/lucid/orm'
5
6
  import { compose } from '@adonisjs/core/helpers'
6
7
  import { withAuthUser, withCredentials } from '@adonis-agora/authkit-server'
7
8
 
@@ -17,6 +18,34 @@ import { withAuthUser, withCredentials } from '@adonis-agora/authkit-server'
17
18
  * (e aponte as migrations correspondentes para essa conexão).
18
19
  */
19
20
  export default class AuthUser extends compose(BaseModel, withAuthUser(), withCredentials()) {
21
+ /**
22
+ * Sem isto, o Lucid ignora o id atribuído pelo hook abaixo e sobrescreve
23
+ * `id` com o retorno bruto do INSERT (o rowid interno do SQLite/Postgres)
24
+ * assim que a linha é salva — mesmo resultado prático de não ter o hook: a
25
+ * conta some do próprio id que acabou de criar.
26
+ */
27
+ static selfAssignPrimaryKey = true
28
+
20
29
  @column({ isPrimary: true })
21
30
  declare id: string
31
+
32
+ /**
33
+ * Nem withAuthUser() nem withCredentials() geram o id — sem este hook o
34
+ * Lucid insere NULL nesta coluna string e o `id` do model volta como o
35
+ * rowid interno do banco, tornando a conta inalcançável pelo id real na
36
+ * próxima request.
37
+ */
38
+ @beforeCreate()
39
+ static assignUuid(user: AuthUser) {
40
+ user.id = randomUUID()
41
+ }
42
+
43
+ /**
44
+ * A tela de signup embutida sempre coleta um campo "Nome", e o Lucid store
45
+ * passa esse valor direto para `AuthUser.create()` — sem esta coluna o
46
+ * primeiro signup quebra com "Cannot define 'fullName' on 'AuthUser'
47
+ * model, since it is not defined as a model property".
48
+ */
49
+ @column()
50
+ declare fullName: string | null
22
51
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adonis-agora/authkit-server",
3
- "version": "0.65.1",
3
+ "version": "0.65.3",
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",
@@ -66,7 +66,7 @@
66
66
  "@adonisjs/drive": "^4.0.0",
67
67
  "@adonisjs/lock": "^2.1.0",
68
68
  "@adonisjs/lucid": "^22.4.2",
69
- "@adonisjs/redis": "^9.2.0 || ^10.0.0",
69
+ "@adonisjs/redis": "^9.2.0 || ^10.0.0 || ^11.0.0",
70
70
  "@adonisjs/session": "^8.1.0",
71
71
  "@adonisjs/shield": "^9.0.0",
72
72
  "@vinejs/vine": "^4.4.0",
@@ -0,0 +1,47 @@
1
+ {{{
2
+ exports({
3
+ to: app.makePath(migration.folder, entity.path, migration.fileName)
4
+ })
5
+ }}}
6
+ import { BaseSchema } from '@adonisjs/lucid/schema'
7
+
8
+ export default class extends BaseSchema {
9
+ protected tableName = 'auth_users'
10
+
11
+ async up() {
12
+ this.schema.createTable(this.tableName, (table) => {
13
+ /**
14
+ * String, NÃO increments(): o hook `@beforeCreate` do model scaffoldado
15
+ * (`models/auth_user.stub`) atribui um `randomUUID()` antes do insert.
16
+ * Uma coluna auto-increment aceitaria o INSERT mesmo assim, mas o valor
17
+ * do UUID seria descartado e o `id` do model voltaria como o rowid
18
+ * interno do banco — a conta ficaria inalcançável pelo id real na
19
+ * próxima request. Ver packages/authkit-server/stubs/models/auth_user.stub.
20
+ */
21
+ table.string('id').notNullable().primary()
22
+
23
+ // withAuthUser() — packages/authkit-server/src/mixins/with_auth_user.ts
24
+ table.string('email').notNullable().unique()
25
+ table.string('password').notNullable()
26
+ // Serializado como JSON string ("[]" quando vazio) pelo mixin.
27
+ table.json('global_roles').notNullable().defaultTo('[]')
28
+
29
+ // withCredentials() — packages/authkit-server/src/mixins/with_credentials.ts
30
+ table.timestamp('email_verified_at', { useTz: true }).nullable()
31
+ table.string('email_verification_token').nullable()
32
+ table.string('password_reset_token').nullable()
33
+ table.timestamp('password_reset_expires_at', { useTz: true }).nullable()
34
+
35
+ // Coluna própria do model scaffoldado (não vem de nenhum mixin): a tela
36
+ // de signup embutida sempre coleta "Nome" e o Lucid store passa esse
37
+ // valor direto para `AuthUser.create()` — sem esta coluna o primeiro
38
+ // signup quebra com "Cannot define 'fullName' on 'AuthUser' model,
39
+ // since it is not defined as a model property".
40
+ table.string('full_name').nullable()
41
+ })
42
+ }
43
+
44
+ async down() {
45
+ this.schema.dropTable(this.tableName)
46
+ }
47
+ }
@@ -1,7 +1,8 @@
1
1
  {{{
2
2
  exports({ to: app.makePath('app/models/auth_user.ts') })
3
3
  }}}
4
- import { BaseModel, column } from '@adonisjs/lucid/orm'
4
+ import { randomUUID } from 'node:crypto'
5
+ import { BaseModel, beforeCreate, column } from '@adonisjs/lucid/orm'
5
6
  import { compose } from '@adonisjs/core/helpers'
6
7
  import { withAuthUser, withCredentials } from '@adonis-agora/authkit-server'
7
8
 
@@ -17,6 +18,34 @@ import { withAuthUser, withCredentials } from '@adonis-agora/authkit-server'
17
18
  * (e aponte as migrations correspondentes para essa conexão).
18
19
  */
19
20
  export default class AuthUser extends compose(BaseModel, withAuthUser(), withCredentials()) {
21
+ /**
22
+ * Sem isto, o Lucid ignora o id atribuído pelo hook abaixo e sobrescreve
23
+ * `id` com o retorno bruto do INSERT (o rowid interno do SQLite/Postgres)
24
+ * assim que a linha é salva — mesmo resultado prático de não ter o hook: a
25
+ * conta some do próprio id que acabou de criar.
26
+ */
27
+ static selfAssignPrimaryKey = true
28
+
20
29
  @column({ isPrimary: true })
21
30
  declare id: string
31
+
32
+ /**
33
+ * Nem withAuthUser() nem withCredentials() geram o id — sem este hook o
34
+ * Lucid insere NULL nesta coluna string e o `id` do model volta como o
35
+ * rowid interno do banco, tornando a conta inalcançável pelo id real na
36
+ * próxima request.
37
+ */
38
+ @beforeCreate()
39
+ static assignUuid(user: AuthUser) {
40
+ user.id = randomUUID()
41
+ }
42
+
43
+ /**
44
+ * A tela de signup embutida sempre coleta um campo "Nome", e o Lucid store
45
+ * passa esse valor direto para `AuthUser.create()` — sem esta coluna o
46
+ * primeiro signup quebra com "Cannot define 'fullName' on 'AuthUser'
47
+ * model, since it is not defined as a model property".
48
+ */
49
+ @column()
50
+ declare fullName: string | null
22
51
  }