@adonis-agora/authkit-server 0.59.0 → 0.61.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.
Files changed (27) hide show
  1. package/build/index.d.ts +4 -2
  2. package/build/index.js +3 -1
  3. package/build/providers/authkit_server_provider.js +7 -0
  4. package/build/src/accounts/account_store.d.ts +20 -1
  5. package/build/src/accounts/account_store.js +4 -0
  6. package/build/src/accounts/lucid_account_store.js +4 -0
  7. package/build/src/accounts/lucid_store/login_methods.d.ts +11 -0
  8. package/build/src/accounts/lucid_store/login_methods.js +32 -0
  9. package/build/src/define_config.d.ts +23 -0
  10. package/build/src/define_config.js +6 -0
  11. package/build/src/host/account_api/account_api_controller.d.ts +38 -0
  12. package/build/src/host/account_api/account_api_controller.js +84 -3
  13. package/build/src/host/auth_host_config.d.ts +9 -0
  14. package/build/src/host/controllers/headless_login_methods_controller.d.ts +31 -0
  15. package/build/src/host/controllers/headless_login_methods_controller.js +79 -0
  16. package/build/src/host/controllers/interaction_controller.js +159 -12
  17. package/build/src/host/controllers/social_controller.js +29 -1
  18. package/build/src/host/login_methods_state.d.ts +39 -0
  19. package/build/src/host/login_methods_state.js +52 -0
  20. package/build/src/host/register_auth_host.js +18 -0
  21. package/build/src/host/ui-dist/assets/{index-6cE5JMyP.js → index-Cb38N16r.js} +1 -1
  22. package/build/src/host/ui-dist/index.html +1 -1
  23. package/build/src/host/user_login_methods.d.ts +94 -0
  24. package/build/src/host/user_login_methods.js +132 -0
  25. package/package.json +7 -4
  26. package/skills/authkit-idp-setup/SKILL.md +222 -0
  27. package/skills/authkit-interactions/SKILL.md +236 -0
package/build/index.d.ts CHANGED
@@ -23,8 +23,8 @@ export { lucidAccountStore, appKeyEncrypter } from './src/accounts/lucid_account
23
23
  export { lucidStores } from './src/accounts/lucid_stores.js';
24
24
  export type { LucidStoresModels, LucidStoresOptions, LucidStoresResult, } from './src/accounts/lucid_stores.js';
25
25
  export type { LucidAccountStoreOptions, AccountSecretEncrypter, } from './src/accounts/lucid_account_store.js';
26
- export type { AccountStore, CoreAccountStore, AdminCapability, MfaCapability, WebauthnCapability, ProviderIdentityCapability, ProviderIdentitySummary, AccountSecurityCapability, AccountStatusCapability, ProfileCapability, MagicLinkCapability, OtpLoginCapability, OtpLoginVerifyResult, EmailVerificationStatusCapability, AccountDeletionCapability, AccountImportCapability, ImportAccountInput, AuthAccount, CreateAccountInput, LinkProviderIdentityInput, ListAccountsParams, Paginated, PasskeySummary, } from './src/accounts/account_store.js';
27
- export { supportsMfa, supportsPasskeys, supportsProviderIdentity, supportsAccountSecurity, supportsAccountStatus, supportsProfile, supportsMagicLink, supportsOtpLogin, supportsEmailVerificationStatus, supportsAccountDeletion, supportsAccountImport, } from './src/accounts/account_store.js';
26
+ export type { AccountStore, CoreAccountStore, AdminCapability, MfaCapability, WebauthnCapability, ProviderIdentityCapability, ProviderIdentitySummary, AccountSecurityCapability, AccountStatusCapability, ProfileCapability, MagicLinkCapability, OtpLoginCapability, OtpLoginVerifyResult, EmailVerificationStatusCapability, AccountDeletionCapability, AccountImportCapability, ImportAccountInput, AuthAccount, CreateAccountInput, LinkProviderIdentityInput, ListAccountsParams, Paginated, PasskeySummary, LoginMethodsPreferenceCapability, } from './src/accounts/account_store.js';
27
+ export { supportsMfa, supportsPasskeys, supportsProviderIdentity, supportsAccountSecurity, supportsAccountStatus, supportsProfile, supportsMagicLink, supportsOtpLogin, supportsEmailVerificationStatus, supportsAccountDeletion, supportsAccountImport, supportsLoginMethodsPreference, } from './src/accounts/account_store.js';
28
28
  export { type OtpLoginConfigInput, type ResolvedOtpLoginConfig, type OtpVerifyOutcome, resolveOtpLoginConfig, generateOtpCode, evaluateLoginOtp, OTP_LOGIN_DEFAULTS, } from './src/host/otp_login.js';
29
29
  export { PasswordManager, PasswordPolicyError, } from './src/password/password_manager.js';
30
30
  export type { PasswordConfigInput, LegacyPasswordVerifier, PasswordVerifyResult, } from './src/password/password_manager.js';
@@ -125,6 +125,8 @@ export { resolveEffectiveBotProtection } from './src/host/bot_protection.js';
125
125
  export type { BotProtectionSetting } from './src/host/bot_protection.js';
126
126
  export { SETTING_KEYS, resolveEffectiveRegistration, resolveEffectiveRequireVerifiedEmail, resolveEffectiveMaintenanceMode, resolveEffectiveAuthMethods, configLockedAuthMethods, } from './src/host/runtime_toggles.js';
127
127
  export type { SettingKey, RegistrationSetting, RequireVerifiedEmailSetting, MaintenanceModeSetting, ResolvedMaintenanceMode, AuthMethodsSetting, ResolvedAuthMethods, AuthMethodsCapabilities, AuthMethodsConfigOverride, } from './src/host/runtime_toggles.js';
128
+ export { USER_LOGIN_METHOD_KEYS, normalizeUserLoginMethods, parseUserLoginMethodsPayload, resolveEffectiveUserLoginMethods, } from './src/host/user_login_methods.js';
129
+ export type { UserLoginMethodKey, UserLoginMethods, ResolvedUserLoginMethods, } from './src/host/user_login_methods.js';
128
130
  export { resolveRegistration } from './src/define_config.js';
129
131
  export type { RegistrationConfigInput, ResolvedRegistrationConfig, } from './src/define_config.js';
130
132
  export { getAccountId, realAccountId, hasAccountSession, consoleLoginUrl, } from './src/host/console_session.js';
package/build/index.js CHANGED
@@ -14,7 +14,7 @@ export { resolveTrustedDevices, isTrustedDeviceValid, buildTrustedDevicePayload,
14
14
  export { resolveBotProtection, botProtectionApplies, extractBotToken, verifyBotProtection, guardBotProtection, DEFAULT_BOT_TOKEN_FIELDS, } from './src/host/bot_protection.js';
15
15
  export { lucidAccountStore, appKeyEncrypter } from './src/accounts/lucid_account_store.js';
16
16
  export { lucidStores } from './src/accounts/lucid_stores.js';
17
- export { supportsMfa, supportsPasskeys, supportsProviderIdentity, supportsAccountSecurity, supportsAccountStatus, supportsProfile, supportsMagicLink, supportsOtpLogin, supportsEmailVerificationStatus, supportsAccountDeletion, supportsAccountImport, } from './src/accounts/account_store.js';
17
+ export { supportsMfa, supportsPasskeys, supportsProviderIdentity, supportsAccountSecurity, supportsAccountStatus, supportsProfile, supportsMagicLink, supportsOtpLogin, supportsEmailVerificationStatus, supportsAccountDeletion, supportsAccountImport, supportsLoginMethodsPreference, } from './src/accounts/account_store.js';
18
18
  // Login por OTP (código digitável): config + helpers puros.
19
19
  export { resolveOtpLoginConfig, generateOtpCode, evaluateLoginOtp, OTP_LOGIN_DEFAULTS, } from './src/host/otp_login.js';
20
20
  // Gerência de senha: lazy rehash + legacy verifier, política e checagem de vazamento.
@@ -85,6 +85,8 @@ export { buildKeysStatus, rotateNow } from './src/host/key_rotation_actions.js';
85
85
  export { resolveEffectiveBotProtection } from './src/host/bot_protection.js';
86
86
  // Runtime toggles (registration, require_verified_email, maintenance_mode).
87
87
  export { SETTING_KEYS, resolveEffectiveRegistration, resolveEffectiveRequireVerifiedEmail, resolveEffectiveMaintenanceMode, resolveEffectiveAuthMethods, configLockedAuthMethods, } from './src/host/runtime_toggles.js';
88
+ // Tipos de login POR USUÁRIO (self-service no console de conta).
89
+ export { USER_LOGIN_METHOD_KEYS, normalizeUserLoginMethods, parseUserLoginMethodsPayload, resolveEffectiveUserLoginMethods, } from './src/host/user_login_methods.js';
88
90
  export { resolveRegistration } from './src/define_config.js';
89
91
  export { getAccountId, realAccountId, hasAccountSession, consoleLoginUrl, } from './src/host/console_session.js';
90
92
  export { ACCOUNT_SESSION_KEY } from './src/host/account_session_key.js';
@@ -77,6 +77,13 @@ export default class AuthkitServerProvider {
77
77
  // Defaults estruturais de `config.routes` (o argumento ainda vence).
78
78
  routes: typeof config.routes === 'object' ? config.routes : undefined,
79
79
  lockedRouteOptions: config.lockedRouteOptions,
80
+ // API headless — repassa pro registerAuthHost montar as rotas.
81
+ headless: config.headless
82
+ ? {
83
+ baseUrl: config.headless.baseUrl,
84
+ resolveAccountId: config.headless.resolveAccountId,
85
+ }
86
+ : undefined,
80
87
  });
81
88
  }
82
89
  }
@@ -1,4 +1,5 @@
1
1
  /** DTO público da conta — o que o provider e os controllers enxergam. Nunca um model Lucid. */
2
+ import type { UserLoginMethods } from '../host/user_login_methods.js';
2
3
  export interface AuthAccount {
3
4
  id: string;
4
5
  email: string;
@@ -211,6 +212,22 @@ export interface ProviderIdentitySummary {
211
212
  providerUserId: string;
212
213
  email?: string | null;
213
214
  }
215
+ /**
216
+ * Preferência POR USUÁRIO de tipos de login (self-service no console de conta).
217
+ * CAPACIDADE opcional, montada quando o model tem a coluna `login_methods`
218
+ * (JSONB) — hosts adotam por migração própria. `null` = sem preferência (herda
219
+ * os métodos globais efetivos). Ver `host/user_login_methods.ts` para o shape
220
+ * canônico (`UserLoginMethods`) e a semântica da interseção.
221
+ */
222
+ export interface LoginMethodsPreferenceCapability {
223
+ /** Lê a preferência da conta; null quando não há preferência persistida. */
224
+ getLoginMethods(accountId: string): Promise<UserLoginMethods | null>;
225
+ /**
226
+ * Grava a preferência da conta. Objeto vazio ou null REMOVE a preferência
227
+ * (volta a herdar os globais). No-op se a conta não existe.
228
+ */
229
+ setLoginMethods(accountId: string, methods: UserLoginMethods | null): Promise<void>;
230
+ }
214
231
  /**
215
232
  * MFA / TOTP. Stores sem suporte a MFA omitem a capacidade inteira; o interaction
216
233
  * flow trata a ausência como "MFA desligado".
@@ -599,7 +616,7 @@ export type AccountStore = CoreAccountStore & {
599
616
  * blocos `Partial<...>` de capacidades probáveis.
600
617
  */
601
618
  readonly connectionName?: string;
602
- } & Partial<MfaCapability & WebauthnCapability & ProviderIdentityCapability & AccountSecurityCapability & AccountStatusCapability & ProfileCapability & MagicLinkCapability & OtpLoginCapability & EmailVerificationStatusCapability & AccountDeletionCapability & AccountImportCapability & OrganizationsCapability & PasswordHistoryCapability & PasswordExpirationCapability>;
619
+ } & Partial<MfaCapability & WebauthnCapability & ProviderIdentityCapability & AccountSecurityCapability & AccountStatusCapability & ProfileCapability & MagicLinkCapability & OtpLoginCapability & EmailVerificationStatusCapability & AccountDeletionCapability & AccountImportCapability & OrganizationsCapability & PasswordHistoryCapability & PasswordExpirationCapability & LoginMethodsPreferenceCapability>;
603
620
  /** Type guard: o store implementa a capacidade de MFA / TOTP. */
604
621
  export declare function supportsMfa(store: AccountStore): store is AccountStore & MfaCapability;
605
622
  /**
@@ -634,3 +651,5 @@ export declare function supportsAccountImport(store: AccountStore): store is Acc
634
651
  export declare function supportsPasswordHistory(store: AccountStore): store is AccountStore & PasswordHistoryCapability;
635
652
  /** Type guard: o store implementa expiração de senha (password_changed_at coluna). */
636
653
  export declare function supportsPasswordExpiration(store: AccountStore): store is AccountStore & PasswordExpirationCapability;
654
+ /** Type guard: o store implementa a preferência por usuário de tipos de login. */
655
+ export declare function supportsLoginMethodsPreference(store: AccountStore): store is AccountStore & LoginMethodsPreferenceCapability;
@@ -63,3 +63,7 @@ export function supportsPasswordHistory(store) {
63
63
  export function supportsPasswordExpiration(store) {
64
64
  return typeof store.getPasswordChangedAt === 'function';
65
65
  }
66
+ /** Type guard: o store implementa a preferência por usuário de tipos de login. */
67
+ export function supportsLoginMethodsPreference(store) {
68
+ return typeof store.getLoginMethods === 'function';
69
+ }
@@ -1,6 +1,7 @@
1
1
  import { generateAuthenticationOptions, generateRegistrationOptions, verifyAuthenticationResponse, verifyRegistrationResponse, } from '@simplewebauthn/server';
2
2
  import { PasswordManager } from '../password/password_manager.js';
3
3
  import { buildCore } from './lucid_store/core.js';
4
+ import { buildLoginMethods, supportsLoginMethodsColumn } from './lucid_store/login_methods.js';
4
5
  import { buildMfa } from './lucid_store/mfa.js';
5
6
  import { buildOrganizations } from './lucid_store/organizations.js';
6
7
  import { buildPasswordExpiration, buildPasswordHistory } from './lucid_store/password_hygiene.js';
@@ -212,6 +213,9 @@ export function lucidAccountStore(Model, options = {}) {
212
213
  ...buildDeletion(ctx),
213
214
  // Expiração de senha: só quando o model tem a coluna `password_changed_at`.
214
215
  ...(hasColumn(Model, 'passwordChangedAt') ? buildPasswordExpiration(ctx) : {}),
216
+ // Preferência por usuário de tipos de login: só quando o model tem a coluna
217
+ // `login_methods` (JSONB). Sem a coluna → capacidade ausente (feature no-op).
218
+ ...(supportsLoginMethodsColumn(Model) ? buildLoginMethods(ctx) : {}),
215
219
  // Organizations (multi-tenancy): só quando os três models foram fornecidos.
216
220
  ...(OrgModels
217
221
  ? buildOrganizations({
@@ -0,0 +1,11 @@
1
+ import type { LoginMethodsPreferenceCapability } from '../account_store.js';
2
+ import type { LucidStoreContext } from './shared.js';
3
+ /**
4
+ * Preferência por usuário de tipos de login sobre a coluna `login_methods`
5
+ * (JSONB) do model. Só deve ser montada quando a coluna existe
6
+ * ({@link hasColumn}) — caso contrário a capacidade fica ausente e a feature
7
+ * degrada para no-op (hosts adotam por migração própria).
8
+ */
9
+ export declare function buildLoginMethods(ctx: LucidStoreContext): LoginMethodsPreferenceCapability;
10
+ /** Indica se o model suporta a preferência de tipos de login (coluna presente). */
11
+ export declare function supportsLoginMethodsColumn(Model: any): boolean;
@@ -0,0 +1,32 @@
1
+ import { normalizeUserLoginMethods } from '../../host/user_login_methods.js';
2
+ import { hasColumn } from './status_profile.js';
3
+ /**
4
+ * Preferência por usuário de tipos de login sobre a coluna `login_methods`
5
+ * (JSONB) do model. Só deve ser montada quando a coluna existe
6
+ * ({@link hasColumn}) — caso contrário a capacidade fica ausente e a feature
7
+ * degrada para no-op (hosts adotam por migração própria).
8
+ */
9
+ export function buildLoginMethods(ctx) {
10
+ const { Model } = ctx;
11
+ return {
12
+ async getLoginMethods(accountId) {
13
+ const row = await Model.find(accountId);
14
+ if (!row)
15
+ return null;
16
+ return normalizeUserLoginMethods(row.loginMethods);
17
+ },
18
+ async setLoginMethods(accountId, methods) {
19
+ const row = await Model.find(accountId);
20
+ if (!row)
21
+ return;
22
+ // Objeto vazio/null → grava null (sem preferência = herda globais).
23
+ const value = methods && Object.keys(methods).length > 0 ? normalizeUserLoginMethods(methods) : null;
24
+ row.loginMethods = value;
25
+ await row.save();
26
+ },
27
+ };
28
+ }
29
+ /** Indica se o model suporta a preferência de tipos de login (coluna presente). */
30
+ export function supportsLoginMethodsColumn(Model) {
31
+ return hasColumn(Model, 'loginMethods');
32
+ }
@@ -822,6 +822,24 @@ export interface AuthServerConfigInput {
822
822
  i18n?: I18nConfig;
823
823
  /** Configuração de providers sociais. */
824
824
  social?: AuthSocialConfig;
825
+ /**
826
+ * API headless do AuthKit (Clerk-style). Quando declarada, o host-kit monta
827
+ * uma JSON API (`GET/PUT {baseUrl}/login-methods`) autenticada por um resolver
828
+ * de conta fornecido pelo HOST (`resolveAccountId`) — tipicamente a própria
829
+ * sessão do RP (`await ctx.auth.getUserOrFail()`). Assim um app usa AuthKit
830
+ * como frontend sem expor o console `/account/*`. Default: desligado (opt-in).
831
+ */
832
+ headless?: {
833
+ /** Base da JSON API headless. Default: `/api/authkit`. */
834
+ baseUrl?: string;
835
+ /**
836
+ * Resolve o id da conta (na identidade do IdP) a partir do contexto da
837
+ * request do HOST. Retorne null quando não autenticado → 401. Um `throw`
838
+ * do resolver também vira 401. O id deve ser o mesmo `sub`/`AuthAccount.id`
839
+ * usado pelo account store (no entre-textos, `auth.user.id === app.user.id`).
840
+ */
841
+ resolveAccountId: (ctx: import('@adonisjs/core/http').HttpContext) => string | null;
842
+ };
825
843
  /** Segredo para autenticar requests de introspecção de PAT. */
826
844
  patIntrospectionSecret?: string;
827
845
  /** Rate-limiting das rotas sensíveis (anti-brute-force). Default: ligado (no-op se o limiter não estiver configurado). */
@@ -1075,6 +1093,11 @@ export interface ResolvedServerConfig {
1075
1093
  firstPartyClients?: string[];
1076
1094
  social?: AuthSocialConfig;
1077
1095
  patIntrospectionSecret?: string;
1096
+ /** API headless resolvida (Clerk-style). Presente só quando o host a declarou. */
1097
+ headless?: {
1098
+ baseUrl: string;
1099
+ resolveAccountId: (ctx: import('@adonisjs/core/http').HttpContext) => string | null;
1100
+ };
1078
1101
  rateLimit: ResolvedRateLimitConfig;
1079
1102
  /** Bloqueio progressivo de conta resolvido (sempre presente; default ligado). */
1080
1103
  lockout: ResolvedLockoutConfig;
@@ -395,6 +395,12 @@ export function defineConfig(config) {
395
395
  passwordless: resolvePasswordless(config.passwordless),
396
396
  authMethods: resolveAuthMethodsConfig(config.authMethods),
397
397
  login: resolveLogin(config.login),
398
+ headless: config.headless
399
+ ? {
400
+ baseUrl: config.headless.baseUrl ?? '/api/authkit',
401
+ resolveAccountId: config.headless.resolveAccountId,
402
+ }
403
+ : undefined,
398
404
  registration: resolveRegistration(config.registration),
399
405
  accessTokens: resolveAccessTokens(config.issuer, config.accessTokens),
400
406
  admin: resolveAdmin(config.admin),
@@ -30,6 +30,7 @@
30
30
  import '../augmentations.js';
31
31
  import type { HttpContext } from '@adonisjs/core/http';
32
32
  export default class AccountApiController {
33
+ #private;
33
34
  /** Perfil + flags do usuário logado. */
34
35
  me(ctx: HttpContext): Promise<void | {
35
36
  id: any;
@@ -171,6 +172,43 @@ export default class AccountApiController {
171
172
  available: any;
172
173
  };
173
174
  }>;
175
+ /**
176
+ * Preferência POR USUÁRIO de tipos de login + o catálogo do que está
177
+ * disponível/travado. A interseção com os métodos globais efetivos é feita
178
+ * AQUI (a UI mostra o estado final; o enforcement no login reusa o resolver).
179
+ */
180
+ getLoginMethods(ctx: HttpContext): Promise<{
181
+ supported: false;
182
+ methods: null;
183
+ available: null;
184
+ locked: null;
185
+ } | {
186
+ supported: true;
187
+ methods: any;
188
+ available: {
189
+ password: boolean;
190
+ magicLink: boolean;
191
+ passkey: boolean;
192
+ social: string[];
193
+ forgotPassword: boolean;
194
+ };
195
+ locked: {
196
+ password: boolean;
197
+ magicLink: boolean;
198
+ passkey: boolean;
199
+ social: boolean;
200
+ };
201
+ }>;
202
+ /** Grava a preferência de tipos de login do usuário logado. */
203
+ updateLoginMethods(ctx: HttpContext): Promise<void | {
204
+ ok: boolean;
205
+ methods: {
206
+ password?: boolean;
207
+ magicLink?: boolean;
208
+ passkey?: boolean;
209
+ social?: boolean;
210
+ };
211
+ }>;
174
212
  /** Lista passkeys do usuário logado. */
175
213
  listPasskeys(ctx: HttpContext): Promise<{
176
214
  supported: boolean;
@@ -28,7 +28,7 @@
28
28
  * GET /account/api/orgs/invitations → convites pendentes
29
29
  */
30
30
  import '../augmentations.js';
31
- import { supportsAccountSecurity, supportsOrganizations, supportsPasskeys, supportsProfile, } from '../../accounts/account_store.js';
31
+ import { supportsAccountSecurity, supportsLoginMethodsPreference, supportsMagicLink, supportsOrganizations, supportsPasskeys, supportsProfile, } from '../../accounts/account_store.js';
32
32
  import { PasswordPolicyError } from '../../password/password_manager.js';
33
33
  import { accountPath } from '../account_paths.js';
34
34
  import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
@@ -39,11 +39,12 @@ import { AvatarUploadError, isAvatarUploadSupported, storeAvatar } from '../avat
39
39
  import { sendEmailChangeConfirmationEmail, sendEmailChangeNoticeEmail } from '../default_mailer.js';
40
40
  import { translate } from '../i18n.js';
41
41
  import { authkitOrigin } from '../origin.js';
42
- import { resolveRuntimeSettings } from '../runtime_settings.js';
43
- import { resolveEffectiveEmailChange, resolveEffectivePasswordHistory, } from '../runtime_toggles.js';
42
+ import { resolveRuntimeSettings, resolveRuntimeSettingsOrNoop } from '../runtime_settings.js';
43
+ import { configLockedAuthMethods, resolveEffectiveAuthMethods, resolveEffectiveEmailChange, resolveEffectivePasswordHistory, } from '../runtime_toggles.js';
44
44
  import { dispatchSecurityNotice } from '../security_notice_service.js';
45
45
  import { enrichSessionsWithContext } from '../session_context.js';
46
46
  import { SUDO_MODE_DEFAULTS, isSudoActive, requireSudo, resolveEffectiveSudoMode, } from '../sudo_mode.js';
47
+ import { parseUserLoginMethodsPayload, resolveEffectiveUserLoginMethods, } from '../user_login_methods.js';
47
48
  import { changeEmailValidator, changePasswordValidator, updateProfileValidator, } from '../validators.js';
48
49
  // ---------------------------------------------------------------------------
49
50
  // Helpers (shared with existing controllers — kept local to avoid coupling)
@@ -641,6 +642,86 @@ export default class AccountApiController {
641
642
  recovery: { available: enabled },
642
643
  };
643
644
  }
645
+ // ─── GET /account/api/login-methods ─────────────────────────────────────
646
+ /**
647
+ * Preferência POR USUÁRIO de tipos de login + o catálogo do que está
648
+ * disponível/travado. A interseção com os métodos globais efetivos é feita
649
+ * AQUI (a UI mostra o estado final; o enforcement no login reusa o resolver).
650
+ */
651
+ async getLoginMethods(ctx) {
652
+ const service = await ctx.containerResolver.make('authkit.server');
653
+ const cfg = service.config;
654
+ const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
655
+ const supported = supportsLoginMethodsPreference(cfg.accountStore);
656
+ if (!supported) {
657
+ return { supported: false, methods: null, available: null, locked: null };
658
+ }
659
+ const pref = await cfg.accountStore.getLoginMethods(userId);
660
+ const { resolved: global, locked: cfgLocked } = await this.#resolveGlobalAuthMethods(ctx, cfg);
661
+ const effective = resolveEffectiveUserLoginMethods(global, pref);
662
+ return {
663
+ supported: true,
664
+ // Preferência crua do usuário (o que ELE escolheu; null = sem preferência).
665
+ methods: pref ?? {},
666
+ // Estado final por método (global ∩ preferência) — o que a tela de login mostra.
667
+ available: {
668
+ password: effective.password,
669
+ magicLink: effective.magicLink,
670
+ passkey: effective.passkey,
671
+ social: effective.social,
672
+ forgotPassword: effective.forgotPassword,
673
+ },
674
+ // Métodos que o usuário NÃO pode controlar (globalmente off ou pin de config).
675
+ // Pin de config: `cfgLocked` lista as chaves fixadas no arquivo de config.
676
+ locked: {
677
+ password: !global.password || cfgLocked.includes('password'),
678
+ magicLink: !global.magicLink || cfgLocked.includes('magicLink'),
679
+ passkey: !global.passkey || cfgLocked.includes('passkey'),
680
+ social: global.social.length === 0,
681
+ },
682
+ };
683
+ }
684
+ // ─── PUT /account/api/login-methods ─────────────────────────────────────
685
+ /** Grava a preferência de tipos de login do usuário logado. */
686
+ async updateLoginMethods(ctx) {
687
+ const service = await ctx.containerResolver.make('authkit.server');
688
+ const cfg = service.config;
689
+ const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
690
+ if (!supportsLoginMethodsPreference(cfg.accountStore)) {
691
+ return ctx.response.notFound(apiErr('not_supported', 'Login methods preference not supported.'));
692
+ }
693
+ const parsed = parseUserLoginMethodsPayload(ctx.request.body());
694
+ if (!parsed.ok) {
695
+ return ctx.response.badRequest(apiErr(parsed.error, 'Invalid login methods payload.'));
696
+ }
697
+ // All-off guard server-side: a preferência não pode zerar todos os métodos
698
+ // GLOBAIS disponíveis. Um `false` que deixaria zero métodos é recusado.
699
+ const { resolved: global } = await this.#resolveGlobalAuthMethods(ctx, cfg);
700
+ const value = { ...parsed.value };
701
+ const wouldBeAllOff = (value.password === false || !global.password) &&
702
+ (value.magicLink === false || !global.magicLink) &&
703
+ (value.passkey === false || !global.passkey) &&
704
+ (value.social === false || global.social.length === 0);
705
+ if (wouldBeAllOff) {
706
+ return ctx.response.badRequest(apiErr('all_methods_off', 'Cannot disable every login method.'));
707
+ }
708
+ await cfg.accountStore.setLoginMethods(userId, Object.keys(value).length > 0 ? value : null);
709
+ return { ok: true, methods: value };
710
+ }
711
+ /**
712
+ * Métodos globais efetivos + pins de config, compartilhados pelos handlers
713
+ * acima. Fail-safe: erro → defaults conservadores.
714
+ */
715
+ async #resolveGlobalAuthMethods(ctx, cfg) {
716
+ const settings = await resolveRuntimeSettingsOrNoop(ctx);
717
+ const resolved = await resolveEffectiveAuthMethods(settings, {
718
+ configuredSocialProviders: cfg.social?.providers ?? [],
719
+ magicLinkCapable: cfg.passwordless?.magicLink && supportsMagicLink(cfg.accountStore),
720
+ passkeyCapable: supportsPasskeys(cfg.accountStore),
721
+ configOverrides: cfg.authMethods,
722
+ });
723
+ return { resolved, locked: configLockedAuthMethods(cfg.authMethods) };
724
+ }
644
725
  // ─── GET /account/api/passkeys ──────────────────────────────────────────
645
726
  /** Lista passkeys do usuário logado. */
646
727
  async listPasskeys(ctx) {
@@ -1,3 +1,4 @@
1
+ import type { HttpContext } from '@adonisjs/core/http';
1
2
  import type { AuthSocialConfig, ResolvedRateLimitConfig } from '../define_config.js';
2
3
  import type { PolicyRouteOption } from './config_locks.js';
3
4
  import type { AuthHostOptions } from './register_auth_host.js';
@@ -34,6 +35,14 @@ export interface AuthHostRuntimeConfig {
34
35
  * Ver `deriveLockedRouteOptions`.
35
36
  */
36
37
  lockedRouteOptions?: PolicyRouteOption[];
38
+ /**
39
+ * API headless (Clerk-style). Presente quando o host a declarou — o provider o
40
+ * stash no boot para `registerAuthHost` montar as rotas sem reler o config inteiro.
41
+ */
42
+ headless?: {
43
+ baseUrl: string;
44
+ resolveAccountId: (ctx: HttpContext) => string | null;
45
+ };
37
46
  }
38
47
  /** Stash dos bits de routing (chamado no boot do provider). */
39
48
  export declare function setAuthHostConfig(config: AuthHostRuntimeConfig): void;
@@ -0,0 +1,31 @@
1
+ import type { HttpContext } from '@adonisjs/core/http';
2
+ /**
3
+ * API headless do AuthKit (Clerk-style).
4
+ *
5
+ * Diferente da JSON API do console (`/account/api/*`, guardada pela sessão de
6
+ * CONTA), esta API é protegida por um resolver de conta fornecido pelo HOST
7
+ * (`headless.resolveAccountId`) — tipicamente a própria sessão do app
8
+ * (`await ctx.auth.getUserOrFail()`). Assim um RP integrado usa o app como
9
+ * frontend sem expor o console `/account/*`.
10
+ */
11
+ export default class HeadlessLoginMethodsController {
12
+ #private;
13
+ /**
14
+ * GET {baseUrl}/login-methods
15
+ * Estado final de tipos de login para a conta resolvida pelo host.
16
+ */
17
+ index(ctx: HttpContext): Promise<void | import("../login_methods_state.js").LoginMethodsState>;
18
+ /**
19
+ * PUT {baseUrl}/login-methods
20
+ * Grava a preferência de tipos de login para a conta resolvida pelo host.
21
+ */
22
+ update(ctx: HttpContext): Promise<void | {
23
+ ok: boolean;
24
+ methods: {
25
+ password?: boolean;
26
+ magicLink?: boolean;
27
+ passkey?: boolean;
28
+ social?: boolean;
29
+ };
30
+ }>;
31
+ }
@@ -0,0 +1,79 @@
1
+ import { supportsLoginMethodsPreference } from '../../accounts/account_store.js';
2
+ import { loginMethodsStateForAccount, resolveGlobalAuthMethods } from '../login_methods_state.js';
3
+ import { resolveRuntimeSettingsOrNoop } from '../runtime_settings.js';
4
+ import { parseUserLoginMethodsPayload } from '../user_login_methods.js';
5
+ /** Erro JSON padrão. */
6
+ function apiErr(code, message) {
7
+ return { error: { code, message } };
8
+ }
9
+ /**
10
+ * API headless do AuthKit (Clerk-style).
11
+ *
12
+ * Diferente da JSON API do console (`/account/api/*`, guardada pela sessão de
13
+ * CONTA), esta API é protegida por um resolver de conta fornecido pelo HOST
14
+ * (`headless.resolveAccountId`) — tipicamente a própria sessão do app
15
+ * (`await ctx.auth.getUserOrFail()`). Assim um RP integrado usa o app como
16
+ * frontend sem expor o console `/account/*`.
17
+ */
18
+ export default class HeadlessLoginMethodsController {
19
+ /**
20
+ * GET {baseUrl}/login-methods
21
+ * Estado final de tipos de login para a conta resolvida pelo host.
22
+ */
23
+ async index(ctx) {
24
+ const service = await ctx.containerResolver.make('authkit.server');
25
+ const cfg = service.config;
26
+ const accountId = await this.#resolveAccount(ctx, cfg);
27
+ if (!accountId) {
28
+ return ctx.response.unauthorized(apiErr('unauthorized', 'Not authenticated.'));
29
+ }
30
+ const settings = await resolveRuntimeSettingsOrNoop(ctx);
31
+ return loginMethodsStateForAccount(accountId, cfg.accountStore, settings, cfg);
32
+ }
33
+ /**
34
+ * PUT {baseUrl}/login-methods
35
+ * Grava a preferência de tipos de login para a conta resolvida pelo host.
36
+ */
37
+ async update(ctx) {
38
+ const service = await ctx.containerResolver.make('authkit.server');
39
+ const cfg = service.config;
40
+ const accountId = await this.#resolveAccount(ctx, cfg);
41
+ if (!accountId) {
42
+ return ctx.response.unauthorized(apiErr('unauthorized', 'Not authenticated.'));
43
+ }
44
+ if (!supportsLoginMethodsPreference(cfg.accountStore)) {
45
+ return ctx.response.notFound(apiErr('not_supported', 'Login methods preference not supported.'));
46
+ }
47
+ const parsed = parseUserLoginMethodsPayload(ctx.request.body());
48
+ if (!parsed.ok) {
49
+ return ctx.response.badRequest(apiErr(parsed.error, 'Invalid login methods payload.'));
50
+ }
51
+ // All-off guard: preferência não pode zerar todos os métodos globais.
52
+ const settings = await resolveRuntimeSettingsOrNoop(ctx);
53
+ const { resolved: global } = await resolveGlobalAuthMethods(settings, cfg);
54
+ const value = { ...parsed.value };
55
+ const wouldBeAllOff = (value.password === false || !global.password) &&
56
+ (value.magicLink === false || !global.magicLink) &&
57
+ (value.passkey === false || !global.passkey) &&
58
+ (value.social === false || global.social.length === 0);
59
+ if (wouldBeAllOff) {
60
+ return ctx.response.badRequest(apiErr('all_methods_off', 'Cannot disable every login method.'));
61
+ }
62
+ await cfg.accountStore.setLoginMethods(accountId, Object.keys(value).length > 0 ? value : null);
63
+ return { ok: true, methods: value };
64
+ }
65
+ /** Delega ao resolver do host; null (ou throw de auth) => 401. */
66
+ async #resolveAccount(ctx, cfg) {
67
+ const resolve = cfg.headless?.resolveAccountId;
68
+ if (!resolve)
69
+ return null;
70
+ try {
71
+ const id = await resolve(ctx);
72
+ return id ? String(id) : null;
73
+ }
74
+ catch {
75
+ // Resolver do host lançou (ex.: auth.getUserOrFail sem sessão) → não-autorizado.
76
+ return null;
77
+ }
78
+ }
79
+ }