@adonis-agora/authkit-server 0.58.2 → 0.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.d.ts +1 -1
- package/build/index.js +1 -1
- package/build/src/accounts/lucid_account_store.js +17 -2
- package/build/src/audit/audit_sink.d.ts +10 -0
- package/build/src/doctor/checks.d.ts +13 -0
- package/build/src/doctor/checks.js +79 -0
- package/build/src/events/dispatcher.d.ts +7 -3
- package/build/src/events/dispatcher.js +9 -3
- package/build/src/host/account_api/account_api_controller.js +1 -1
- package/build/src/host/account_session_key.d.ts +21 -0
- package/build/src/host/account_session_key.js +21 -0
- package/build/src/host/admin_api/admin_orgs_service.js +8 -0
- package/build/src/host/admin_console/console_impersonation_controller.js +1 -1
- package/build/src/host/admin_console/console_orgs_controller.js +1 -1
- package/build/src/host/admin_console/console_roles_controller.js +1 -1
- package/build/src/host/admin_console/console_sessions_controller.js +1 -1
- package/build/src/host/admin_console/console_users_controller.js +1 -1
- package/build/src/host/console_session.js +1 -1
- package/build/src/host/controllers/account_apps_controller.js +1 -1
- package/build/src/host/controllers/account_mfa_controller.js +1 -1
- package/build/src/host/controllers/account_orgs_controller.js +14 -3
- package/build/src/host/controllers/account_security_controller.js +1 -1
- package/build/src/host/controllers/account_session_controller.js +1 -1
- package/build/src/host/controllers/account_tokens_controller.js +1 -1
- package/build/src/host/impersonation_session.js +1 -1
- package/build/src/host/middleware/account_auth.d.ts +2 -1
- package/build/src/host/middleware/account_auth.js +2 -1
- package/build/src/host/oidc_rp_guard.js +1 -1
- package/build/src/host/register_auth_host.js +1 -1
- package/build/src/host/sudo/runtime.js +1 -1
- package/build/src/host/sudo_mode.js +1 -1
- package/package.json +3 -3
package/build/index.d.ts
CHANGED
|
@@ -128,7 +128,7 @@ export type { SettingKey, RegistrationSetting, RequireVerifiedEmailSetting, Main
|
|
|
128
128
|
export { resolveRegistration } from './src/define_config.js';
|
|
129
129
|
export type { RegistrationConfigInput, ResolvedRegistrationConfig, } from './src/define_config.js';
|
|
130
130
|
export { getAccountId, realAccountId, hasAccountSession, consoleLoginUrl, } from './src/host/console_session.js';
|
|
131
|
-
export { ACCOUNT_SESSION_KEY } from './src/host/
|
|
131
|
+
export { ACCOUNT_SESSION_KEY } from './src/host/account_session_key.js';
|
|
132
132
|
export { rememberAccessToken, rememberRefreshToken, refreshAccessToken, startImpersonation, impersonationState, stopImpersonation, } from './src/host/impersonation_session.js';
|
|
133
133
|
export type { StartImpersonationParams, ImpersonationState, } from './src/host/impersonation_session.js';
|
|
134
134
|
export { SUDO_SESSION_KEY, SUDO_ACCOUNT_SESSION_KEY, SUDO_MODE_DEFAULTS, requireSudo, isSudoActive, markSudo, resolveEffectiveSudoMode, } from './src/host/sudo_mode.js';
|
package/build/index.js
CHANGED
|
@@ -87,7 +87,7 @@ export { resolveEffectiveBotProtection } from './src/host/bot_protection.js';
|
|
|
87
87
|
export { SETTING_KEYS, resolveEffectiveRegistration, resolveEffectiveRequireVerifiedEmail, resolveEffectiveMaintenanceMode, resolveEffectiveAuthMethods, configLockedAuthMethods, } from './src/host/runtime_toggles.js';
|
|
88
88
|
export { resolveRegistration } from './src/define_config.js';
|
|
89
89
|
export { getAccountId, realAccountId, hasAccountSession, consoleLoginUrl, } from './src/host/console_session.js';
|
|
90
|
-
export { ACCOUNT_SESSION_KEY } from './src/host/
|
|
90
|
+
export { ACCOUNT_SESSION_KEY } from './src/host/account_session_key.js';
|
|
91
91
|
// Session impersonation — RP-side glue that routes through the IdP's RFC 8693
|
|
92
92
|
// token-exchange (the IdP validates the admin role + audits). See
|
|
93
93
|
// src/host/impersonation_session.ts.
|
|
@@ -165,7 +165,11 @@ export function lucidAccountStore(Model, options = {}) {
|
|
|
165
165
|
return encrypter.decrypt(stored);
|
|
166
166
|
},
|
|
167
167
|
toAccount: (row) => ({
|
|
168
|
-
id
|
|
168
|
+
// `String(...)`: o `id` do AuthAccount vira o claim `sub`, que a spec OIDC
|
|
169
|
+
// exige que seja string. Tabelas adotadas em brownfield quase sempre têm
|
|
170
|
+
// `id` INTEGER auto-increment, e sem a coerção o número vazava para o
|
|
171
|
+
// token (e para todo call-site tipado como string).
|
|
172
|
+
id: String(row.id),
|
|
169
173
|
email: row.email,
|
|
170
174
|
globalRoles: row.globalRoles ?? [],
|
|
171
175
|
name: row.fullName ?? undefined,
|
|
@@ -251,6 +255,16 @@ export function lucidAccountStore(Model, options = {}) {
|
|
|
251
255
|
*/
|
|
252
256
|
__mfaIssuer: mfaIssuer,
|
|
253
257
|
__webauthn: options.webauthn,
|
|
258
|
+
/**
|
|
259
|
+
* O model Lucid por trás deste store — exposto para o `authkit:doctor`
|
|
260
|
+
* inspecionar `$columnsDefinitions` e avisar sobre colunas ausentes ANTES
|
|
261
|
+
* que o fluxo correspondente quebre em produção (ver
|
|
262
|
+
* `doctor/checks.ts#checkAccountStoreColumns`). Importa sobretudo em
|
|
263
|
+
* adoção brownfield, onde o model aponta para uma tabela `users` que já
|
|
264
|
+
* existia e não passou pelas migrations do authkit.
|
|
265
|
+
* NÃO faz parte do contrato AccountStore.
|
|
266
|
+
*/
|
|
267
|
+
__model: Model,
|
|
254
268
|
};
|
|
255
269
|
// Histórico de senhas: capability-probed via tabela `auth_password_history`.
|
|
256
270
|
// A versão síncrona não pode fazer o probe de DB, então a capability fica
|
|
@@ -287,7 +301,8 @@ export async function lucidAccountStoreAsync(Model, options = {}) {
|
|
|
287
301
|
sealSecret: (s) => s,
|
|
288
302
|
openSecret: (s) => s ?? null,
|
|
289
303
|
toAccount: (row) => ({
|
|
290
|
-
|
|
304
|
+
// Ver a nota em `toAccount` acima: `sub` precisa ser string.
|
|
305
|
+
id: String(row.id),
|
|
291
306
|
email: row.email,
|
|
292
307
|
globalRoles: row.globalRoles ?? [],
|
|
293
308
|
name: row.fullName ?? undefined,
|
|
@@ -21,6 +21,16 @@ export interface AuditEvent {
|
|
|
21
21
|
clientId?: string | null;
|
|
22
22
|
/** Impersonation: quem agiu (o admin). */
|
|
23
23
|
actorId?: string | null;
|
|
24
|
+
/**
|
|
25
|
+
* Organização (tenant) a que o evento pertence, quando houver. Campo de
|
|
26
|
+
* PRIMEIRA CLASSE — e não uma chave de `metadata` — de propósito: `metadata`
|
|
27
|
+
* é livre e por isso é DROPADO na projeção que vai para o barramento de
|
|
28
|
+
* diagnostics (ver `redactAuditEventForDiagnostics`), enquanto os ids internos
|
|
29
|
+
* opacos (`accountId`/`actorId`/`clientId`/`orgId`) são preservados. É esse
|
|
30
|
+
* campo que permite a um consumidor do barramento — p.ex. o provisioning do
|
|
31
|
+
* `@adonis-agora/authz` — saber QUAL tenant provisionar sem receber PII.
|
|
32
|
+
*/
|
|
33
|
+
orgId?: string | null;
|
|
24
34
|
ip?: string | null;
|
|
25
35
|
metadata?: Record<string, unknown>;
|
|
26
36
|
}
|
|
@@ -57,6 +57,19 @@ export declare function checkClients(input: DoctorInput): Finding;
|
|
|
57
57
|
export declare function checkAdapterVolatility(input: DoctorInput): Finding | null;
|
|
58
58
|
/** accountStore presente + quais capacidades implementa. */
|
|
59
59
|
export declare function checkAccountStore(input: DoctorInput): Finding[];
|
|
60
|
+
/**
|
|
61
|
+
* Valida que o model por trás do account store tem as colunas que o store
|
|
62
|
+
* escreve. Existe por causa da adoção brownfield: quando o host aponta o
|
|
63
|
+
* AuthKit para a tabela `users` que ele JÁ tinha, as colunas dos mixins
|
|
64
|
+
* (`password_reset_token`, `email_verification_token`, …) não existem, o app
|
|
65
|
+
* sobe normalmente, e a falha só aparece quando alguém clica em "esqueci minha
|
|
66
|
+
* senha" — em produção. Este check antecipa isso para o boot.
|
|
67
|
+
*
|
|
68
|
+
* Silencioso quando não há o que inspecionar: um store custom (não-Lucid) não
|
|
69
|
+
* expõe `__model`, e nesse caso o contrato é responsabilidade de quem o
|
|
70
|
+
* implementou.
|
|
71
|
+
*/
|
|
72
|
+
export declare function checkAccountStoreColumns(input: DoctorInput): Finding[];
|
|
60
73
|
/** session provider configurado + warn se cookie store com tokenSets grandes. */
|
|
61
74
|
export declare function checkSession(input: DoctorInput): Finding[];
|
|
62
75
|
/** Hint de exceções de CSRF do shield para o mountPath. */
|
|
@@ -134,6 +134,84 @@ export function checkAccountStore(input) {
|
|
|
134
134
|
});
|
|
135
135
|
return findings;
|
|
136
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Propriedades do model que o account store Lucid escreve/lê, agrupadas pelo
|
|
139
|
+
* FLUXO que deixa de funcionar quando a coluna não existe. Agrupar por fluxo (e
|
|
140
|
+
* não listar colunas soltas) é o que torna o achado acionável: "falta
|
|
141
|
+
* `password_reset_token`" não diz nada a quem não conhece o interior da lib;
|
|
142
|
+
* "o reset de senha vai quebrar" diz.
|
|
143
|
+
*/
|
|
144
|
+
const REQUIRED_COLUMN_GROUPS = [
|
|
145
|
+
{ flow: 'identity (every flow)', properties: ['email'] },
|
|
146
|
+
{ flow: 'password login', properties: ['password'] },
|
|
147
|
+
{ flow: 'role claims / admin', properties: ['globalRoles'] },
|
|
148
|
+
{
|
|
149
|
+
flow: 'password reset',
|
|
150
|
+
properties: ['passwordResetToken', 'passwordResetExpiresAt'],
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
flow: 'email verification',
|
|
154
|
+
properties: ['emailVerifiedAt', 'emailVerificationToken'],
|
|
155
|
+
},
|
|
156
|
+
];
|
|
157
|
+
/**
|
|
158
|
+
* Propriedades OPCIONAIS: a capability correspondente é detectada pela presença
|
|
159
|
+
* da coluna, então ausência é configuração válida (feature desligada) e nunca
|
|
160
|
+
* um erro — só vale reportar para que a ausência seja uma escolha, e não uma
|
|
161
|
+
* surpresa.
|
|
162
|
+
*/
|
|
163
|
+
const OPTIONAL_COLUMN_GROUPS = [
|
|
164
|
+
{ capability: 'profile (name/avatar)', properties: ['fullName', 'avatarUrl'] },
|
|
165
|
+
{ capability: 'disable/enable account', properties: ['disabledAt'] },
|
|
166
|
+
{ capability: 'password expiration', properties: ['passwordChangedAt'] },
|
|
167
|
+
];
|
|
168
|
+
/**
|
|
169
|
+
* Valida que o model por trás do account store tem as colunas que o store
|
|
170
|
+
* escreve. Existe por causa da adoção brownfield: quando o host aponta o
|
|
171
|
+
* AuthKit para a tabela `users` que ele JÁ tinha, as colunas dos mixins
|
|
172
|
+
* (`password_reset_token`, `email_verification_token`, …) não existem, o app
|
|
173
|
+
* sobe normalmente, e a falha só aparece quando alguém clica em "esqueci minha
|
|
174
|
+
* senha" — em produção. Este check antecipa isso para o boot.
|
|
175
|
+
*
|
|
176
|
+
* Silencioso quando não há o que inspecionar: um store custom (não-Lucid) não
|
|
177
|
+
* expõe `__model`, e nesse caso o contrato é responsabilidade de quem o
|
|
178
|
+
* implementou.
|
|
179
|
+
*/
|
|
180
|
+
export function checkAccountStoreColumns(input) {
|
|
181
|
+
const store = input.authkitConfig?.accountStore;
|
|
182
|
+
const model = store?.__model;
|
|
183
|
+
const definitions = model?.$columnsDefinitions;
|
|
184
|
+
if (!definitions || typeof definitions.get !== 'function')
|
|
185
|
+
return [];
|
|
186
|
+
/** Nome da coluna real (o que o DBA procura), com fallback à propriedade. */
|
|
187
|
+
const columnOf = (property) => definitions.get(property)?.columnName ?? property;
|
|
188
|
+
const missing = (properties) => properties.filter((p) => !definitions.has(p));
|
|
189
|
+
const findings = [];
|
|
190
|
+
for (const { flow, properties } of REQUIRED_COLUMN_GROUPS) {
|
|
191
|
+
const absent = missing(properties);
|
|
192
|
+
if (absent.length === 0)
|
|
193
|
+
continue;
|
|
194
|
+
const named = absent.map((p) => `\`${p}\` (column \`${columnOf(p)}\`)`).join(', ');
|
|
195
|
+
findings.push({
|
|
196
|
+
level: 'error',
|
|
197
|
+
message: `accountStore model (${model.name ?? 'model'}) is missing ${named} — ${flow} will fail at runtime. Add the column(s) with a migration, or map an existing one with @column({ columnName: '…' }).`,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
const off = OPTIONAL_COLUMN_GROUPS.filter((g) => missing(g.properties).length > 0);
|
|
201
|
+
if (off.length > 0) {
|
|
202
|
+
findings.push({
|
|
203
|
+
level: 'ok',
|
|
204
|
+
message: `Capabilities off (column absent): ${off.map((g) => g.capability).join(', ')}.`,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
if (findings.every((f) => f.level === 'ok')) {
|
|
208
|
+
findings.unshift({
|
|
209
|
+
level: 'ok',
|
|
210
|
+
message: `accountStore model (${model.name ?? 'model'}) has every required column.`,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
return findings;
|
|
214
|
+
}
|
|
137
215
|
/** session provider configurado + warn se cookie store com tokenSets grandes. */
|
|
138
216
|
export function checkSession(input) {
|
|
139
217
|
if (!input.peers.session) {
|
|
@@ -845,6 +923,7 @@ export function runAllChecks(input) {
|
|
|
845
923
|
if (volatility)
|
|
846
924
|
findings.push(volatility);
|
|
847
925
|
findings.push(...checkAccountStore(input));
|
|
926
|
+
findings.push(...checkAccountStoreColumns(input));
|
|
848
927
|
findings.push(...checkSession(input));
|
|
849
928
|
findings.push(checkShield(input));
|
|
850
929
|
findings.push(checkAlly(input));
|
|
@@ -56,9 +56,13 @@ export declare function signWebhookBody(body: string, secret: string): string;
|
|
|
56
56
|
* e-mail). Nenhum data provider do dashboard lê `metadata`, então dropá-lo é
|
|
57
57
|
* seguro;
|
|
58
58
|
* - MANTÉM `type` (a família do evento — o que os providers agregam) e os ids
|
|
59
|
-
* internos opacos `accountId`/`actorId`/`clientId` (correlação de
|
|
60
|
-
* no dashboard; NÃO são PII direta e, sem `email`/`ip
|
|
61
|
-
* da conta já deletada, não são reidentificáveis).
|
|
59
|
+
* internos opacos `accountId`/`actorId`/`clientId`/`orgId` (correlação de
|
|
60
|
+
* subject/actor/tenant no dashboard; NÃO são PII direta e, sem `email`/`ip`/
|
|
61
|
+
* `metadata` e com a linha da conta já deletada, não são reidentificáveis).
|
|
62
|
+
* O `orgId` está aqui porque é o que torna o barramento UTILIZÁVEL para
|
|
63
|
+
* provisioning multi-tenant (o `@adonis-agora/authz` escuta
|
|
64
|
+
* `agora:authkit:organization.*` e precisa saber qual tenant escopar) —
|
|
65
|
+
* sem ele o consumidor recebia o tipo do evento e mais nada.
|
|
62
66
|
*
|
|
63
67
|
* Assim o Telescope nunca armazena PII bruta e a deleção de conta não precisa de uma
|
|
64
68
|
* etapa de purge cross-lib. Os ramos `onEvent`/`webhook` (integrações que o host
|
|
@@ -20,6 +20,7 @@ export function buildWebhookBody(event) {
|
|
|
20
20
|
accountId: event.accountId ?? null,
|
|
21
21
|
email: event.email ?? null,
|
|
22
22
|
clientId: event.clientId ?? null,
|
|
23
|
+
orgId: event.orgId ?? null,
|
|
23
24
|
ip: event.ip ?? null,
|
|
24
25
|
metadata: event.metadata ?? {},
|
|
25
26
|
ts: new Date().toISOString(),
|
|
@@ -50,9 +51,13 @@ export function signWebhookBody(body, secret) {
|
|
|
50
51
|
* e-mail). Nenhum data provider do dashboard lê `metadata`, então dropá-lo é
|
|
51
52
|
* seguro;
|
|
52
53
|
* - MANTÉM `type` (a família do evento — o que os providers agregam) e os ids
|
|
53
|
-
* internos opacos `accountId`/`actorId`/`clientId` (correlação de
|
|
54
|
-
* no dashboard; NÃO são PII direta e, sem `email`/`ip
|
|
55
|
-
* da conta já deletada, não são reidentificáveis).
|
|
54
|
+
* internos opacos `accountId`/`actorId`/`clientId`/`orgId` (correlação de
|
|
55
|
+
* subject/actor/tenant no dashboard; NÃO são PII direta e, sem `email`/`ip`/
|
|
56
|
+
* `metadata` e com a linha da conta já deletada, não são reidentificáveis).
|
|
57
|
+
* O `orgId` está aqui porque é o que torna o barramento UTILIZÁVEL para
|
|
58
|
+
* provisioning multi-tenant (o `@adonis-agora/authz` escuta
|
|
59
|
+
* `agora:authkit:organization.*` e precisa saber qual tenant escopar) —
|
|
60
|
+
* sem ele o consumidor recebia o tipo do evento e mais nada.
|
|
56
61
|
*
|
|
57
62
|
* Assim o Telescope nunca armazena PII bruta e a deleção de conta não precisa de uma
|
|
58
63
|
* etapa de purge cross-lib. Os ramos `onEvent`/`webhook` (integrações que o host
|
|
@@ -65,6 +70,7 @@ export function redactAuditEventForDiagnostics(event) {
|
|
|
65
70
|
accountId: event.accountId ?? null,
|
|
66
71
|
actorId: event.actorId ?? null,
|
|
67
72
|
clientId: event.clientId ?? null,
|
|
73
|
+
orgId: event.orgId ?? null,
|
|
68
74
|
};
|
|
69
75
|
}
|
|
70
76
|
/**
|
|
@@ -31,13 +31,13 @@ import '../augmentations.js';
|
|
|
31
31
|
import { supportsAccountSecurity, 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
|
+
import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
|
|
34
35
|
import { ACTIVE_ORG_COOKIE } from '../active_org_cookie.js';
|
|
35
36
|
import { AdminSessionsService } from '../admin_sessions_service.js';
|
|
36
37
|
import { syncAdonisAuthLogout } from '../adonis_auth_sync.js';
|
|
37
38
|
import { AvatarUploadError, isAvatarUploadSupported, storeAvatar } from '../avatar_storage.js';
|
|
38
39
|
import { sendEmailChangeConfirmationEmail, sendEmailChangeNoticeEmail } from '../default_mailer.js';
|
|
39
40
|
import { translate } from '../i18n.js';
|
|
40
|
-
import { ACCOUNT_SESSION_KEY } from '../middleware/account_auth.js';
|
|
41
41
|
import { authkitOrigin } from '../origin.js';
|
|
42
42
|
import { resolveRuntimeSettings } from '../runtime_settings.js';
|
|
43
43
|
import { resolveEffectiveEmailChange, resolveEffectivePasswordHistory, } from '../runtime_toggles.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session key holding the signed-in account id for the account console.
|
|
3
|
+
*
|
|
4
|
+
* A module of its own, deliberately, and it must stay free of imports.
|
|
5
|
+
*
|
|
6
|
+
* The constant is re-exported from the package barrel, and it used to live in
|
|
7
|
+
* `middleware/account_auth.ts` — which side-effect-imports `../augmentations.js`
|
|
8
|
+
* to type `ctx.session` for its own body. That made every consumer of the barrel
|
|
9
|
+
* load `augmentations.d.ts`, and with it this package's
|
|
10
|
+
* `declare module '@adonisjs/core/http' { adminApiKeyId?: string }`.
|
|
11
|
+
*
|
|
12
|
+
* `adminApiKeyId` is a name only this library claims, so the blast radius was
|
|
13
|
+
* small — but that is nomenclature luck, not design. The sibling client package
|
|
14
|
+
* shipped the identical shape over `auth`, a name every app declares, and
|
|
15
|
+
* consumers went from 24 type errors to 351 with nothing pointing at the cause.
|
|
16
|
+
* A constant that hosts import should not be able to carry a `HttpContext`
|
|
17
|
+
* augmentation in with it.
|
|
18
|
+
*
|
|
19
|
+
* Keep this file import-free: anything it imports, the barrel inherits.
|
|
20
|
+
*/
|
|
21
|
+
export declare const ACCOUNT_SESSION_KEY = "account_user_id";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session key holding the signed-in account id for the account console.
|
|
3
|
+
*
|
|
4
|
+
* A module of its own, deliberately, and it must stay free of imports.
|
|
5
|
+
*
|
|
6
|
+
* The constant is re-exported from the package barrel, and it used to live in
|
|
7
|
+
* `middleware/account_auth.ts` — which side-effect-imports `../augmentations.js`
|
|
8
|
+
* to type `ctx.session` for its own body. That made every consumer of the barrel
|
|
9
|
+
* load `augmentations.d.ts`, and with it this package's
|
|
10
|
+
* `declare module '@adonisjs/core/http' { adminApiKeyId?: string }`.
|
|
11
|
+
*
|
|
12
|
+
* `adminApiKeyId` is a name only this library claims, so the blast radius was
|
|
13
|
+
* small — but that is nomenclature luck, not design. The sibling client package
|
|
14
|
+
* shipped the identical shape over `auth`, a name every app declares, and
|
|
15
|
+
* consumers went from 24 type errors to 351 with nothing pointing at the cause.
|
|
16
|
+
* A constant that hosts import should not be able to carry a `HttpContext`
|
|
17
|
+
* augmentation in with it.
|
|
18
|
+
*
|
|
19
|
+
* Keep this file import-free: anything it imports, the barrel inherits.
|
|
20
|
+
*/
|
|
21
|
+
export const ACCOUNT_SESSION_KEY = 'account_user_id';
|
|
@@ -110,6 +110,7 @@ export class AdminOrgsService {
|
|
|
110
110
|
accountId: input.ownerAccountId,
|
|
111
111
|
actorId: actor.actorId,
|
|
112
112
|
ip: actor.ip,
|
|
113
|
+
orgId: org.id,
|
|
113
114
|
metadata: { slug: org.slug, ...(actor.source ? { actor: actor.source } : {}) },
|
|
114
115
|
});
|
|
115
116
|
return org;
|
|
@@ -137,6 +138,7 @@ export class AdminOrgsService {
|
|
|
137
138
|
type: 'organization.updated',
|
|
138
139
|
actorId: actor.actorId,
|
|
139
140
|
ip: actor.ip,
|
|
141
|
+
orgId,
|
|
140
142
|
metadata: { orgId, ...(actor.source ? { actor: actor.source } : {}) },
|
|
141
143
|
});
|
|
142
144
|
return updated;
|
|
@@ -158,6 +160,7 @@ export class AdminOrgsService {
|
|
|
158
160
|
type: 'organization.deleted',
|
|
159
161
|
actorId: actor.actorId,
|
|
160
162
|
ip: actor.ip,
|
|
163
|
+
orgId,
|
|
161
164
|
metadata: { orgId, slug: existing.slug, ...(actor.source ? { actor: actor.source } : {}) },
|
|
162
165
|
});
|
|
163
166
|
return { ok: true };
|
|
@@ -182,6 +185,7 @@ export class AdminOrgsService {
|
|
|
182
185
|
type: 'organization.member_added',
|
|
183
186
|
actorId: actor.actorId,
|
|
184
187
|
ip: actor.ip,
|
|
188
|
+
orgId,
|
|
185
189
|
metadata: {
|
|
186
190
|
orgId,
|
|
187
191
|
accountId: input.accountId,
|
|
@@ -209,6 +213,7 @@ export class AdminOrgsService {
|
|
|
209
213
|
type: 'organization.member_removed',
|
|
210
214
|
actorId: actor.actorId,
|
|
211
215
|
ip: actor.ip,
|
|
216
|
+
orgId,
|
|
212
217
|
metadata: {
|
|
213
218
|
orgId,
|
|
214
219
|
targetAccountId: accountId,
|
|
@@ -239,6 +244,7 @@ export class AdminOrgsService {
|
|
|
239
244
|
type: 'organization.member_role_changed',
|
|
240
245
|
actorId: actor.actorId,
|
|
241
246
|
ip: actor.ip,
|
|
247
|
+
orgId,
|
|
242
248
|
metadata: {
|
|
243
249
|
orgId,
|
|
244
250
|
targetAccountId: accountId,
|
|
@@ -301,6 +307,7 @@ export class AdminOrgsService {
|
|
|
301
307
|
type: 'organization.invitation_sent',
|
|
302
308
|
actorId: actor.actorId,
|
|
303
309
|
ip: actor.ip,
|
|
310
|
+
orgId,
|
|
304
311
|
metadata: {
|
|
305
312
|
orgId,
|
|
306
313
|
email: input.email,
|
|
@@ -327,6 +334,7 @@ export class AdminOrgsService {
|
|
|
327
334
|
type: 'organization.invitation_revoked',
|
|
328
335
|
actorId: actor.actorId,
|
|
329
336
|
ip: actor.ip,
|
|
337
|
+
orgId,
|
|
330
338
|
metadata: {
|
|
331
339
|
orgId,
|
|
332
340
|
invitationId,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import '../augmentations.js';
|
|
2
|
+
import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
|
|
2
3
|
import { apiError } from '../admin_api/dto.js';
|
|
3
4
|
import { AdminClientsService } from '../admin_clients_service.js';
|
|
4
5
|
import { buildImpersonationPanel } from '../impersonation.js';
|
|
5
|
-
import { ACCOUNT_SESSION_KEY } from '../middleware/account_auth.js';
|
|
6
6
|
import { resolveRuntimeSettingsOrNoop } from '../runtime_settings.js';
|
|
7
7
|
import { resolveEffectiveAdminImpersonation } from '../runtime_toggles.js';
|
|
8
8
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import '../augmentations.js';
|
|
2
|
+
import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
|
|
2
3
|
import { AdminOrgsService } from '../admin_api/admin_orgs_service.js';
|
|
3
4
|
import { apiError, orgDetailDto, orgDto } from '../admin_api/dto.js';
|
|
4
5
|
import { orgAddMemberValidator, orgCreateValidator, orgInvitationValidator, orgMemberRoleValidator, orgUpdateValidator, } from '../admin_validators.js';
|
|
5
|
-
import { ACCOUNT_SESSION_KEY } from '../middleware/account_auth.js';
|
|
6
6
|
import { authkitOrigin } from '../origin.js';
|
|
7
7
|
import { resolveRuntimeSettings } from '../runtime_settings.js';
|
|
8
8
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import '../augmentations.js';
|
|
2
|
+
import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
|
|
2
3
|
import { apiError } from '../admin_api/dto.js';
|
|
3
4
|
import { roleCreateValidator, roleUpdateValidator } from '../admin_validators.js';
|
|
4
|
-
import { ACCOUNT_SESSION_KEY } from '../middleware/account_auth.js';
|
|
5
5
|
import { resolveRuntimeSettings } from '../runtime_settings.js';
|
|
6
6
|
import { SETTING_KEYS, resolveEffectiveRolesCatalog, } from '../runtime_toggles.js';
|
|
7
7
|
/** Regex de validação: nome de role — letras maiúsculas, dígitos e underscore. */
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import '../augmentations.js';
|
|
2
|
+
import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
|
|
2
3
|
import { apiError, grantDto, sessionDto } from '../admin_api/dto.js';
|
|
3
4
|
import { AdminSessionsService } from '../admin_sessions_service.js';
|
|
4
5
|
import { sessionAccountValidator } from '../admin_validators.js';
|
|
5
|
-
import { ACCOUNT_SESSION_KEY } from '../middleware/account_auth.js';
|
|
6
6
|
import { enrichSessionsWithContext } from '../session_context.js';
|
|
7
7
|
/**
|
|
8
8
|
* Endpoints JSON de sessões/grants do console admin React.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import '../augmentations.js';
|
|
2
2
|
import { supportsAccountDeletion, supportsAccountStatus } from '../../accounts/account_store.js';
|
|
3
|
+
import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
|
|
3
4
|
import { AdminUsersService } from '../admin_api/admin_users_service.js';
|
|
4
5
|
import { apiError, grantDto, sessionDto, userDto } from '../admin_api/dto.js';
|
|
5
6
|
import { AdminSessionsService } from '../admin_sessions_service.js';
|
|
6
7
|
import { adminUserCreateValidator, adminUserRolesValidator } from '../admin_validators.js';
|
|
7
|
-
import { ACCOUNT_SESSION_KEY } from '../middleware/account_auth.js';
|
|
8
8
|
import { resolveRuntimeSettings } from '../runtime_settings.js';
|
|
9
9
|
import { resolveEffectiveRolesCatalog } from '../runtime_toggles.js';
|
|
10
10
|
import { enrichSessionsWithContext } from '../session_context.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getAccountLoginUrl } from './account_login_url.js';
|
|
2
|
+
import { ACCOUNT_SESSION_KEY } from './account_session_key.js';
|
|
2
3
|
import { impersonationState } from './impersonation_session.js';
|
|
3
|
-
import { ACCOUNT_SESSION_KEY } from './middleware/account_auth.js';
|
|
4
4
|
/**
|
|
5
5
|
* Helpers públicos para integrar a sessão do console do AuthKit com
|
|
6
6
|
* qualquer coisa fora do pacote (ex.: proteger o dashboard do
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import '../augmentations.js';
|
|
2
2
|
import { accountPath } from '../account_paths.js';
|
|
3
|
+
import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
|
|
3
4
|
import { AdminSessionsService } from '../admin_sessions_service.js';
|
|
4
|
-
import { ACCOUNT_SESSION_KEY } from '../middleware/account_auth.js';
|
|
5
5
|
/**
|
|
6
6
|
* Self-service de consentimento ("apps com acesso") no console de conta. Lista os
|
|
7
7
|
* Grants da própria conta agrupados por client (resolvendo o nome do client da
|
|
@@ -2,8 +2,8 @@ import '../augmentations.js';
|
|
|
2
2
|
import QRCode from 'qrcode';
|
|
3
3
|
import { supportsPasskeys } from '../../accounts/account_store.js';
|
|
4
4
|
import { accountPath } from '../account_paths.js';
|
|
5
|
+
import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
|
|
5
6
|
import { translate } from '../i18n.js';
|
|
6
|
-
import { ACCOUNT_SESSION_KEY } from '../middleware/account_auth.js';
|
|
7
7
|
import { resolveRuntimeSettings } from '../runtime_settings.js';
|
|
8
8
|
import { dispatchSecurityNotice } from '../security_notice_service.js';
|
|
9
9
|
import { requireSudo } from '../sudo_mode.js';
|
|
@@ -2,9 +2,9 @@ import '../augmentations.js';
|
|
|
2
2
|
import { supportsOrganizations } from '../../accounts/account_store.js';
|
|
3
3
|
import { getAccountLoginUrl } from '../account_login_url.js';
|
|
4
4
|
import { accountPath } from '../account_paths.js';
|
|
5
|
+
import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
|
|
5
6
|
import { ACTIVE_ORG_COOKIE, ACTIVE_ORG_COOKIE_TTL, encodeActiveOrgCookie, } from '../active_org_cookie.js';
|
|
6
7
|
import { sendOrgInvitationEmail } from '../default_mailer.js';
|
|
7
|
-
import { ACCOUNT_SESSION_KEY } from '../middleware/account_auth.js';
|
|
8
8
|
import { authkitOrigin } from '../origin.js';
|
|
9
9
|
import { resolveRuntimeSettings } from '../runtime_settings.js';
|
|
10
10
|
import { isRoleInCatalog } from '../runtime_toggles.js';
|
|
@@ -72,8 +72,13 @@ export default class AccountOrgsController {
|
|
|
72
72
|
if (!name || !slug)
|
|
73
73
|
return response.redirect(accountPath('orgs'));
|
|
74
74
|
try {
|
|
75
|
-
await store.createOrg({ name, slug, ownerAccountId: accountId });
|
|
76
|
-
await cfg.audit?.record({
|
|
75
|
+
const org = await store.createOrg({ name, slug, ownerAccountId: accountId });
|
|
76
|
+
await cfg.audit?.record({
|
|
77
|
+
type: 'organization.created',
|
|
78
|
+
accountId,
|
|
79
|
+
orgId: org.id,
|
|
80
|
+
metadata: { slug },
|
|
81
|
+
});
|
|
77
82
|
}
|
|
78
83
|
catch {
|
|
79
84
|
// slug duplicado ou outro erro — redireciona sem mensagem de erro específica
|
|
@@ -112,6 +117,7 @@ export default class AccountOrgsController {
|
|
|
112
117
|
await cfg.audit?.record({
|
|
113
118
|
type: 'organization.switched',
|
|
114
119
|
accountId,
|
|
120
|
+
orgId,
|
|
115
121
|
metadata: { orgId, orgSlug: org.slug },
|
|
116
122
|
});
|
|
117
123
|
return response.redirect(accountPath('orgs'));
|
|
@@ -140,6 +146,7 @@ export default class AccountOrgsController {
|
|
|
140
146
|
await cfg.audit?.record({
|
|
141
147
|
type: 'organization.member_removed',
|
|
142
148
|
accountId,
|
|
149
|
+
orgId: params.id,
|
|
143
150
|
metadata: { orgId: params.id, self: true },
|
|
144
151
|
});
|
|
145
152
|
}
|
|
@@ -217,6 +224,7 @@ export default class AccountOrgsController {
|
|
|
217
224
|
await cfg.audit?.record({
|
|
218
225
|
type: 'organization.invitation_sent',
|
|
219
226
|
accountId,
|
|
227
|
+
orgId: params.id,
|
|
220
228
|
metadata: { orgId: params.id, email, role },
|
|
221
229
|
});
|
|
222
230
|
return response.redirect(accountPath('orgs'));
|
|
@@ -270,6 +278,7 @@ export default class AccountOrgsController {
|
|
|
270
278
|
await cfg.audit?.record({
|
|
271
279
|
type: 'organization.invitation_accepted',
|
|
272
280
|
accountId,
|
|
281
|
+
orgId: invitation.organizationId,
|
|
273
282
|
metadata: { orgId: invitation.organizationId, invitationId: invitation.id },
|
|
274
283
|
});
|
|
275
284
|
}
|
|
@@ -294,6 +303,7 @@ export default class AccountOrgsController {
|
|
|
294
303
|
await cfg.audit?.record({
|
|
295
304
|
type: 'organization.member_removed',
|
|
296
305
|
actorId,
|
|
306
|
+
orgId: params.id,
|
|
297
307
|
metadata: { orgId: params.id, targetAccountId: params.accountId },
|
|
298
308
|
});
|
|
299
309
|
}
|
|
@@ -318,6 +328,7 @@ export default class AccountOrgsController {
|
|
|
318
328
|
await cfg.audit?.record({
|
|
319
329
|
type: 'organization.invitation_revoked',
|
|
320
330
|
actorId,
|
|
331
|
+
orgId: params.id,
|
|
321
332
|
metadata: { orgId: params.id, invitationId: params.invId },
|
|
322
333
|
});
|
|
323
334
|
return response.redirect(accountPath('orgs'));
|
|
@@ -5,12 +5,12 @@ import { AccountDeletionService } from '../account_deletion_service.js';
|
|
|
5
5
|
import { AccountExportService } from '../account_export_service.js';
|
|
6
6
|
import { getAccountLoginUrl } from '../account_login_url.js';
|
|
7
7
|
import { accountPath } from '../account_paths.js';
|
|
8
|
+
import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
|
|
8
9
|
import { AdminSessionsService } from '../admin_sessions_service.js';
|
|
9
10
|
import { syncAdonisAuthLogout } from '../adonis_auth_sync.js';
|
|
10
11
|
import { AvatarUploadError, isAvatarUploadSupported, storeAvatar } from '../avatar_storage.js';
|
|
11
12
|
import { sendEmailChangeConfirmationEmail, sendEmailChangeNoticeEmail, sendEmailChangedCompletedEmail, } from '../default_mailer.js';
|
|
12
13
|
import { translate } from '../i18n.js';
|
|
13
|
-
import { ACCOUNT_SESSION_KEY } from '../middleware/account_auth.js';
|
|
14
14
|
import { authkitOrigin } from '../origin.js';
|
|
15
15
|
import { resolveRuntimeSettings } from '../runtime_settings.js';
|
|
16
16
|
import { resolveEffectiveEmailChange, resolveEffectivePasswordHistory, } from '../runtime_toggles.js';
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import '../augmentations.js';
|
|
2
2
|
import { accountHome } from '../account_home.js';
|
|
3
3
|
import { getAccountLoginUrl } from '../account_login_url.js';
|
|
4
|
+
import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
|
|
4
5
|
import { syncAdonisAuthLogin, syncAdonisAuthLogout } from '../adonis_auth_sync.js';
|
|
5
6
|
import { translate } from '../i18n.js';
|
|
6
7
|
import { attemptPasswordLogin } from '../login_attempt.js';
|
|
7
8
|
import { notifyLoginSuccess } from '../login_notify.js';
|
|
8
|
-
import { ACCOUNT_SESSION_KEY } from '../middleware/account_auth.js';
|
|
9
9
|
import { resolveRuntimeSettings } from '../runtime_settings.js';
|
|
10
10
|
import { markSudo } from '../sudo_mode.js';
|
|
11
11
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import '../augmentations.js';
|
|
2
2
|
import { accountPath } from '../account_paths.js';
|
|
3
|
-
import { ACCOUNT_SESSION_KEY } from '../
|
|
3
|
+
import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
|
|
4
4
|
import { resolveRuntimeSettings } from '../runtime_settings.js';
|
|
5
5
|
import { requireSudo } from '../sudo_mode.js';
|
|
6
6
|
export default class AccountTokensController {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ACCOUNT_SESSION_KEY } from './
|
|
1
|
+
import { ACCOUNT_SESSION_KEY } from './account_session_key.js';
|
|
2
2
|
/**
|
|
3
3
|
* Ergonômico de SESSÃO de browser no RP para "personificar" (impersonate) um
|
|
4
4
|
* usuário e navegar como ele — roteado pelo token-exchange RFC 8693 que o IdP já
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import '../augmentations.js';
|
|
2
2
|
import type { HttpContext } from '@adonisjs/core/http';
|
|
3
3
|
import type { NextFn } from '@adonisjs/core/types/http';
|
|
4
|
-
|
|
4
|
+
import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
|
|
5
|
+
export { ACCOUNT_SESSION_KEY };
|
|
5
6
|
export default class AccountAuthMiddleware {
|
|
6
7
|
handle(ctx: HttpContext, next: NextFn): Promise<any>;
|
|
7
8
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import '../augmentations.js';
|
|
2
2
|
import { getAccountLoginUrl } from '../account_login_url.js';
|
|
3
|
-
|
|
3
|
+
import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
|
|
4
|
+
export { ACCOUNT_SESSION_KEY };
|
|
4
5
|
export default class AccountAuthMiddleware {
|
|
5
6
|
async handle(ctx, next) {
|
|
6
7
|
const userId = ctx.session.get(ACCOUNT_SESSION_KEY);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RuntimeException } from '@adonisjs/core/exceptions';
|
|
2
|
-
import { ACCOUNT_SESSION_KEY } from './
|
|
2
|
+
import { ACCOUNT_SESSION_KEY } from './account_session_key.js';
|
|
3
3
|
/** Memo do construtor entre instâncias — o `import()` só paga o custo uma vez. */
|
|
4
4
|
let cachedUnauthorizedAccess;
|
|
5
5
|
/**
|
|
@@ -3,10 +3,10 @@ import { accountHome } from './account_home.js';
|
|
|
3
3
|
import { getAccountLoginUrl, setAccountLoginUrl } from './account_login_url.js';
|
|
4
4
|
import { accountPath, accountPathsMap, accountPrefix, joinAccountPath, setAccountPaths, } from './account_paths.js';
|
|
5
5
|
import { resolveAccountRoles } from './account_roles.js';
|
|
6
|
+
import { ACCOUNT_SESSION_KEY } from './account_session_key.js';
|
|
6
7
|
import { adminApiGuard } from './admin_api/admin_api_guard.js';
|
|
7
8
|
import { normalizeAdminApiPrefix, normalizeAdminPrefix, setAdminApiPrefix, setAdminPrefix, } from './admin_prefix.js';
|
|
8
9
|
import { getAuthHostConfig, markAuthHostAutoMounted, wasAuthHostAutoMounted, } from './auth_host_config.js';
|
|
9
|
-
import { ACCOUNT_SESSION_KEY } from './middleware/account_auth.js';
|
|
10
10
|
import { createAuthThrottles } from './rate_limit.js';
|
|
11
11
|
import { resolveRuntimeSettings } from './runtime_settings.js';
|
|
12
12
|
import { resolveEffectiveSessionPolicy } from './runtime_toggles.js';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { accountHome } from '../account_home.js';
|
|
2
2
|
import { accountPath } from '../account_paths.js';
|
|
3
|
+
import { ACCOUNT_SESSION_KEY } from '../account_session_key.js';
|
|
3
4
|
import { validateReturnTo } from '../controllers/account_session_controller.js';
|
|
4
5
|
import { translate } from '../i18n.js';
|
|
5
|
-
import { ACCOUNT_SESSION_KEY } from '../middleware/account_auth.js';
|
|
6
6
|
import { markSudo } from '../sudo_mode.js';
|
|
7
7
|
/** Último método usado com sucesso — só ordena a tela, não restringe nada. */
|
|
8
8
|
export const LAST_METHOD_SESSION_KEY = 'authkit_sudo_last_method';
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* `SUDO_ACCOUNT_SESSION_KEY`.
|
|
15
15
|
*/
|
|
16
16
|
import { accountPath } from './account_paths.js';
|
|
17
|
-
import { ACCOUNT_SESSION_KEY } from './
|
|
17
|
+
import { ACCOUNT_SESSION_KEY } from './account_session_key.js';
|
|
18
18
|
import { SETTING_KEYS } from './runtime_toggles.js';
|
|
19
19
|
export const SUDO_MODE_DEFAULTS = {
|
|
20
20
|
enabled: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adonis-agora/authkit-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.59.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",
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
"oidc-provider": "9.11.3",
|
|
109
109
|
"otplib": "12.0.1",
|
|
110
110
|
"qrcode": "1.5.4",
|
|
111
|
-
"@adonis-agora/authkit-core": "0.
|
|
111
|
+
"@adonis-agora/authkit-core": "0.8.0"
|
|
112
112
|
},
|
|
113
113
|
"devDependencies": {
|
|
114
114
|
"@adonis-agora/durable": "0.22.0",
|
|
@@ -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.
|
|
153
|
+
"@adonis-agora/authkit-react": "0.19.1"
|
|
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')\"",
|