@adonis-agora/authkit-server 0.59.0 → 0.60.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.
@@ -8,7 +8,7 @@
8
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
9
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
10
  <link href="https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700&family=JetBrains+Mono:ital,wght@0,400;0,500;0,600;1,400&display=swap" rel="stylesheet">
11
- <script type="module" crossorigin src="/__AUTHKIT_BASE__/assets/index-6cE5JMyP.js"></script>
11
+ <script type="module" crossorigin src="/__AUTHKIT_BASE__/assets/index-Cb38N16r.js"></script>
12
12
  <link rel="stylesheet" crossorigin href="/__AUTHKIT_BASE__/assets/index-DTSmD4RU.css">
13
13
  </head>
14
14
  <body>
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Tipos de login POR USUÁRIO — preferência self-service do dono da conta.
3
+ *
4
+ * Diferente do runtime setting global `auth_methods` (que o operador controla via
5
+ * console admin), aqui é o PRÓPRIO usuário quem decide, no console de conta,
6
+ * quais métodos de login ficam habilitados para a SUA conta. A preferência é
7
+ * persistida na coluna `login_methods` (JSONB) de `auth.users` e SEMPRE se
8
+ * intersecta com os métodos globais efetivos: o usuário nunca pode LIGAR um
9
+ * método que o host desligou (config pin ou setting), só restringir.
10
+ *
11
+ * Contratos:
12
+ * - Coluna ausente no model → capacidade AUSENTE → feature no-op (mesmo padrão
13
+ * das demais colunas opcionais; hosts adotam por migração própria).
14
+ * - `NULL` na coluna = sem preferência = herda os globais (comportamento atual).
15
+ * - Fail-safe all-off: uma preferência que zere TODOS os métodos disponíveis
16
+ * deixa de restringir (nunca trancamos o usuário fora da própria conta).
17
+ */
18
+ /** Chaves de método que o usuário pode ligar/desligar para a própria conta. */
19
+ export type UserLoginMethodKey = 'password' | 'magicLink' | 'passkey' | 'social';
20
+ export declare const USER_LOGIN_METHOD_KEYS: readonly UserLoginMethodKey[];
21
+ /**
22
+ * Shape persistido na coluna `login_methods` (JSONB) de `auth.users`.
23
+ * Campos ausentes = herda o global. Campos presentes = ON/OFF explícito.
24
+ */
25
+ export interface UserLoginMethods {
26
+ password?: boolean;
27
+ magicLink?: boolean;
28
+ passkey?: boolean;
29
+ /**
30
+ * O método social como um todo (não por provider — a interseção com os
31
+ * providers configurados já acontece no resolver global).
32
+ */
33
+ social?: boolean;
34
+ }
35
+ /**
36
+ * Preferência NORMALIZADA pronta para consumo: cada campo presente indica o que
37
+ * o usuário escolheu; ausente = herda o global. É o que o store devolve/gravа.
38
+ */
39
+ export type NormalizedUserLoginMethods = UserLoginMethods;
40
+ /**
41
+ * Resultado da interseção global × por-usuário, pronto para as telas/POSTs.
42
+ * Espelha {@link ResolvedAuthMethods} (global) mas já filtrado pela preferência.
43
+ */
44
+ export interface ResolvedUserLoginMethods {
45
+ password: boolean;
46
+ magicLink: boolean;
47
+ passkey: boolean;
48
+ /** Providers sociais finais (global ∩ preferência). */
49
+ social: string[];
50
+ forgotPassword: boolean;
51
+ passkeyAutofill: boolean;
52
+ }
53
+ /**
54
+ * Normaliza um valor cru vindo do DB (JSONB) ou do request body para o shape
55
+ * canônico. Campos inválidos são descartados silenciosamente (fail-safe).
56
+ * Objetos vazios viram null (sem preferência).
57
+ */
58
+ export declare function normalizeUserLoginMethods(raw: unknown): NormalizedUserLoginMethods | null;
59
+ /**
60
+ * Valida e normaliza o payload de update vindo do PUT da account API.
61
+ * Espera `{ methods: {...} }`; só aceita chaves conhecidas com valores booleanos.
62
+ * `{ methods: {} }` = limpar a preferência (voltar a herdar os globais).
63
+ */
64
+ export declare function parseUserLoginMethodsPayload(body: unknown): ParsedUserLoginMethodsPayload;
65
+ export type ParsedUserLoginMethodsPayload = {
66
+ ok: true;
67
+ value: UserLoginMethods;
68
+ } | {
69
+ ok: false;
70
+ error: string;
71
+ };
72
+ /**
73
+ * Interseção dos métodos globais efetivos com a preferência do usuário.
74
+ *
75
+ * Regras:
76
+ * - `pref` null/vazio → globais inalterados (herança).
77
+ * - Campo `false` na preferência DESLIGA o método (só pode restringir).
78
+ * - Campo `true` NUNCA liga método globalmente indisponível (interseção).
79
+ * - FAIL-SAFE all-off: se a preferência zerar todos os métodos disponíveis,
80
+ * volta aos globais (nunca deixar a conta sem nenhum método de entrada).
81
+ * Loga um aviso (console.warn) — sinaliza preferência órfã após o host ter
82
+ * desligado métodos globalmente.
83
+ * - Qualquer erro → globais (a chamada nunca deve derrubar o login).
84
+ */
85
+ export declare function resolveEffectiveUserLoginMethods(global: ResolvedAuthMethodsLike, pref: UserLoginMethods | null | undefined): ResolvedUserLoginMethods;
86
+ /** Mínimo que o resolver precisa dos métodos globais (evita import circular). */
87
+ export interface ResolvedAuthMethodsLike {
88
+ password: boolean;
89
+ magicLink: boolean;
90
+ passkey: boolean;
91
+ social: string[];
92
+ forgotPassword: boolean;
93
+ passkeyAutofill: boolean;
94
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Tipos de login POR USUÁRIO — preferência self-service do dono da conta.
3
+ *
4
+ * Diferente do runtime setting global `auth_methods` (que o operador controla via
5
+ * console admin), aqui é o PRÓPRIO usuário quem decide, no console de conta,
6
+ * quais métodos de login ficam habilitados para a SUA conta. A preferência é
7
+ * persistida na coluna `login_methods` (JSONB) de `auth.users` e SEMPRE se
8
+ * intersecta com os métodos globais efetivos: o usuário nunca pode LIGAR um
9
+ * método que o host desligou (config pin ou setting), só restringir.
10
+ *
11
+ * Contratos:
12
+ * - Coluna ausente no model → capacidade AUSENTE → feature no-op (mesmo padrão
13
+ * das demais colunas opcionais; hosts adotam por migração própria).
14
+ * - `NULL` na coluna = sem preferência = herda os globais (comportamento atual).
15
+ * - Fail-safe all-off: uma preferência que zere TODOS os métodos disponíveis
16
+ * deixa de restringir (nunca trancamos o usuário fora da própria conta).
17
+ */
18
+ export const USER_LOGIN_METHOD_KEYS = [
19
+ 'password',
20
+ 'magicLink',
21
+ 'passkey',
22
+ 'social',
23
+ ];
24
+ // ---------------------------------------------------------------------------
25
+ // Normalização / validação
26
+ // ---------------------------------------------------------------------------
27
+ function boolOrUndefined(v) {
28
+ return typeof v === 'boolean' ? v : undefined;
29
+ }
30
+ /**
31
+ * Normaliza um valor cru vindo do DB (JSONB) ou do request body para o shape
32
+ * canônico. Campos inválidos são descartados silenciosamente (fail-safe).
33
+ * Objetos vazios viram null (sem preferência).
34
+ */
35
+ export function normalizeUserLoginMethods(raw) {
36
+ if (raw === null || raw === undefined)
37
+ return null;
38
+ if (typeof raw !== 'object' || Array.isArray(raw))
39
+ return null;
40
+ const r = raw;
41
+ const out = {};
42
+ for (const key of USER_LOGIN_METHOD_KEYS) {
43
+ const v = boolOrUndefined(r[key]);
44
+ if (v !== undefined)
45
+ out[key] = v;
46
+ }
47
+ return Object.keys(out).length > 0 ? out : null;
48
+ }
49
+ /**
50
+ * Valida e normaliza o payload de update vindo do PUT da account API.
51
+ * Espera `{ methods: {...} }`; só aceita chaves conhecidas com valores booleanos.
52
+ * `{ methods: {} }` = limpar a preferência (voltar a herdar os globais).
53
+ */
54
+ export function parseUserLoginMethodsPayload(body) {
55
+ if (typeof body !== 'object' || body === null || Array.isArray(body)) {
56
+ return { ok: false, error: 'invalid_body' };
57
+ }
58
+ const b = body;
59
+ if (!('methods' in b) || typeof b.methods !== 'object' || b.methods === null) {
60
+ return { ok: false, error: 'invalid_methods' };
61
+ }
62
+ const methods = normalizeUserLoginMethods(b.methods);
63
+ // Objeto com só campos inválidos → trata como reset ({}).
64
+ return { ok: true, value: methods ?? {} };
65
+ }
66
+ // ---------------------------------------------------------------------------
67
+ // Resolver puro — interseção global × por-usuário
68
+ // ---------------------------------------------------------------------------
69
+ /**
70
+ * Interseção dos métodos globais efetivos com a preferência do usuário.
71
+ *
72
+ * Regras:
73
+ * - `pref` null/vazio → globais inalterados (herança).
74
+ * - Campo `false` na preferência DESLIGA o método (só pode restringir).
75
+ * - Campo `true` NUNCA liga método globalmente indisponível (interseção).
76
+ * - FAIL-SAFE all-off: se a preferência zerar todos os métodos disponíveis,
77
+ * volta aos globais (nunca deixar a conta sem nenhum método de entrada).
78
+ * Loga um aviso (console.warn) — sinaliza preferência órfã após o host ter
79
+ * desligado métodos globalmente.
80
+ * - Qualquer erro → globais (a chamada nunca deve derrubar o login).
81
+ */
82
+ export function resolveEffectiveUserLoginMethods(global, pref) {
83
+ try {
84
+ const p = normalizeUserLoginMethods(pref);
85
+ if (!p) {
86
+ return passthrough(global);
87
+ }
88
+ const password = applyPref(global.password, p.password);
89
+ const magicLink = applyPref(global.magicLink, p.magicLink);
90
+ const passkey = applyPref(global.passkey, p.passkey);
91
+ const social = p.social === false ? [] : global.social;
92
+ const resolved = {
93
+ password,
94
+ magicLink,
95
+ passkey,
96
+ social,
97
+ // forgotPassword é derivado do password (mesma derivação do global).
98
+ forgotPassword: password && global.forgotPassword,
99
+ passkeyAutofill: passkey && global.passkeyAutofill,
100
+ };
101
+ const allOff = !resolved.password &&
102
+ !resolved.magicLink &&
103
+ !resolved.passkey &&
104
+ resolved.social.length === 0;
105
+ if (allOff) {
106
+ console.warn('[authkit] user login_methods zerou todos os métodos — ignorando a preferência (fail-safe).');
107
+ return passthrough(global);
108
+ }
109
+ return resolved;
110
+ }
111
+ catch {
112
+ // Nunca derruba o login por causa da preferência.
113
+ return passthrough(global);
114
+ }
115
+ }
116
+ /** Interseção: false do usuário desliga; true não liga o que o global não tem. */
117
+ function applyPref(globalValue, prefValue) {
118
+ if (prefValue === false)
119
+ return false;
120
+ return globalValue;
121
+ }
122
+ /** Cópia dos métodos globais como resultado por-usuário (sem preferência). */
123
+ function passthrough(global) {
124
+ return {
125
+ password: global.password,
126
+ magicLink: global.magicLink,
127
+ passkey: global.passkey,
128
+ social: [...(global.social ?? [])],
129
+ forgotPassword: global.forgotPassword,
130
+ passkeyAutofill: global.passkeyAutofill,
131
+ };
132
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adonis-agora/authkit-server",
3
- "version": "0.59.0",
3
+ "version": "0.60.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",
@@ -150,7 +150,7 @@
150
150
  "react-error-boundary": "6.1.2",
151
151
  "nuqs": "2.9.5",
152
152
  "recharts": "3.10.1",
153
- "@adonis-agora/authkit-react": "0.19.1"
153
+ "@adonis-agora/authkit-react": "0.20.0"
154
154
  },
155
155
  "scripts": {
156
156
  "build": "node scripts/build_host_css.mjs && node scripts/build_webauthn.mjs && node scripts/build_ui.mjs && node -e \"const fs=require('node:fs');for(const d of ['build/stubs','build/host/views'])fs.rmSync(d,{recursive:true,force:true})\" && tsc && node -e \"require('node:fs').cpSync('src/host/assets','build/src/host/assets',{recursive:true})\" && node -e \"require('node:fs').cpSync('stubs','build/stubs',{recursive:true,filter:(s)=>!s.endsWith('.ts')})\" && node -e \"const fs=require('node:fs');if(fs.existsSync('assets'))fs.cpSync('assets','build/assets',{recursive:true})\" && node -e \"require('node:fs').cpSync('src/host/views','build/host/views',{recursive:true})\" && node -e \"const fs=require('node:fs');fs.mkdirSync('build/host/ui',{recursive:true});fs.readdirSync('src/host/ui').filter(f=>f.endsWith('.html')).forEach(f=>fs.copyFileSync('src/host/ui/'+f,'build/host/ui/'+f))\" && node -e \"require('node:fs').copyFileSync('commands/commands.json','build/commands/commands.json')\" && node -e \"const fs=require('node:fs');fs.mkdirSync('build/src/password',{recursive:true});fs.copyFileSync('src/password/common_passwords.txt','build/src/password/common_passwords.txt')\"",