@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.
- package/build/index.d.ts +4 -2
- package/build/index.js +3 -1
- package/build/providers/authkit_server_provider.js +7 -0
- package/build/src/accounts/account_store.d.ts +20 -1
- package/build/src/accounts/account_store.js +4 -0
- package/build/src/accounts/lucid_account_store.js +4 -0
- package/build/src/accounts/lucid_store/login_methods.d.ts +11 -0
- package/build/src/accounts/lucid_store/login_methods.js +32 -0
- package/build/src/define_config.d.ts +23 -0
- package/build/src/define_config.js +6 -0
- package/build/src/host/account_api/account_api_controller.d.ts +38 -0
- package/build/src/host/account_api/account_api_controller.js +84 -3
- package/build/src/host/auth_host_config.d.ts +9 -0
- package/build/src/host/controllers/headless_login_methods_controller.d.ts +31 -0
- package/build/src/host/controllers/headless_login_methods_controller.js +79 -0
- package/build/src/host/controllers/interaction_controller.js +159 -12
- package/build/src/host/controllers/social_controller.js +29 -1
- package/build/src/host/login_methods_state.d.ts +39 -0
- package/build/src/host/login_methods_state.js +52 -0
- package/build/src/host/register_auth_host.js +18 -0
- package/build/src/host/ui-dist/assets/{index-6cE5JMyP.js → index-Cb38N16r.js} +1 -1
- package/build/src/host/ui-dist/index.html +1 -1
- package/build/src/host/user_login_methods.d.ts +94 -0
- package/build/src/host/user_login_methods.js +132 -0
- package/package.json +7 -4
- package/skills/authkit-idp-setup/SKILL.md +222 -0
- 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
|
+
}
|