Skip to content

Commit 1d2a246

Browse files
fix(plugin-auth)!: admin create-user refuses a password outside the deployment's length range (#25927)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9D35eCXEFgjdFBa5wrMSFZSx)_ Fixes #25824 Clause-②: no (narrowing) — admin create-user refuses one request shape it used to run: an explicit `password` shorter than the deployment's `minPasswordLength` (one longer than `maxPasswordLength` was already refused by better-auth, now with set-user-password's code). Nothing is widened. Metadata-protocol: none ## Why `POST /api/v1/auth/admin/create-user` saved a 1-character password, and the user then signed in with it (the card's repro, measured on a stock showcase boot). `set-user-password` refuses the same value with `400 INVALID_REQUEST` "Password must be at least 8 characters", and the user's own change-password refuses it with `400 PASSWORD_TOO_SHORT`. The cause is in better-auth 1.7.12 itself: `admin/create-user` runs `assertPasswordNotTooLong` only, while `admin/set-user-password` runs both `assertPasswordNotTooShort` and `assertPasswordNotTooLong` (`better-auth/dist/plugins/admin/routes.mjs`, read in this worktree's install). Our `runAdminCreateUser` passed the password straight to that `createUser`. Our `resolvePassword` ran only the complexity check, and the length check lived only in the set-user-password runner. ADR-0069 records `password_min_length` / `password_max_length` → `emailAndPassword.minPasswordLength` / `maxPasswordLength` as enforced, and on this door it was not. ## What changed (`packages/plugins/plugin-auth/src/admin-user-endpoints.ts`) - **One length check, in the one place both admin doors resolve a credential.** `resolvePassword` now takes the deployment's range (`AuthContextLike['password']['config']`, better-auth's resolved `minPasswordLength` / `maxPasswordLength`). It refuses a password outside that range with set-user-password's existing refusal: `400 INVALID_REQUEST`, "Password must be at least N characters" / "Password must be at most N characters". There is no new error code. set-user-password's own copy of the check is deleted, so the two doors cannot drift apart again. - **create-user reads the range before anything is written.** It reads `getAuthContext()` before `resolvePassword`, so a refusal comes before the multi-org join check, the permission-set grant, `createUser`, the must-change stamp and the audit row. If the context cannot be read, the request is refused through `mapAuthApiError` (`create-user failed`) and no account is created. That is the same mapping set-user-password already used for the same read. - **A generated password is sized to the range.** It is `min(max(16, minPasswordLength), maxPasswordLength)` long, and `generateTemporaryPassword` still never goes below 12. Before, it was always 16. So with a minimum above 16, set-user-password refused the password it had just generated, and create-user saved it below the minimum. The range check runs on generated passwords too, so one that still cannot fit (a maximum below 12) is refused, never saved. - Check order on set-user-password is unchanged: `userId`, then complexity, then length. The one difference is that the context read moved ahead of password resolution, which is observable only when that read throws. Changeset: `.changeset/25824-admin-create-user-password-length.md` (`@objectstack/plugin-auth` patch, BREAKING banner, ADR-0087 `not-required (no-migration-prescription)`, before / now / fix). ## Verification Run at `5737e51e` (code) and `d151a987` (code + changeset; the code blob is identical). - **New pins** (`admin-user-endpoints.test.ts`, describe "admin doors: one password length range"), run across both doors from one table: - Refusal pins: `'a'` (the repro), one short of the minimum, one past the maximum. Each answers `400`, `INVALID_REQUEST` and the exact message, and writes nothing (no `createUser`, no hash, no account write, no engine update, no audit insert). - Boundary pins: exactly the minimum and exactly the maximum are applied. - Configured-range pin: a configured `minPasswordLength: 12` refuses a 10-character password. - Generated-password pins: a minimum of 24 gives a 24-character password, and a maximum of 14 gives a 14-character one. - create-user only: an unreadable auth context refuses with no account. - `pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2 src/admin-user-endpoints.test.ts src/durability-swallow-repair.test.ts`: VERDICT command-exit 0, 2 files, 115/115. - `pnpm --filter @objectstack/plugin-auth test` at `d151a987`: VERDICT command-exit 0, Test Files 193 passed (193), Tests 3698 passed | 10 skipped (3708). - `pnpm --filter @objectstack/plugin-auth build && pnpm --filter @objectstack/plugin-auth typecheck`: VERDICT command-exit 0 (`tsc` + examples + `check:test-typecheck` OK, ledger unchanged: 10 files / 88 errors / 23 pinned signatures). - **Ablations.** Each was committed first, mutated through `scripts/ablation-replace.mjs` or `git restore --source`, and restored to the HEAD blob `b89b9184` with `git diff HEAD` empty: - **BASE** (the file put back to `cb0a2fcf`, tests kept): 9 failed / 84 passed. All three create-user refusal pins, create-user's configured-range pin, all four generated-password pins (both doors) and the unreadable-context pin went red. set-user-password's refusal pins stayed green, as expected: that door already refused. - **A** (the range check deleted from `resolvePassword`): 9 failed / 84 passed. All six refusal pins (both doors), both configured-range pins, and the pre-existing "enforces better-auth min password length" went red. - **B** (`<` → `<=` on the minimum): 4 failed / 89 passed. Both exactly-the-minimum pins and both minimum-raised generated pins went red. - **Gates.** All run at `d151a987`, derived by `node scripts/pm/dispatch-gates.mjs --commands` (70 families). Exit codes were captured before any pipe, and `--ran` reconciles 70 derived: 68 run, 2 NOT MEASURED, 0 unrun. - 66 exited 0 on the first run. - `check-plugin-teardown-shape --self-test` first exited 3 (prerequisite: the shallow clone could not reach its pinned fixture commit). It exited 0 after fetching that one commit. - `pnpm check:dts-closure` first exited 1, naming `@objectstack/metadata-core` d.ts as missing. That package was being rebuilt by my own concurrent dogfood-closure build at that moment. It exited 0 on a re-run once that build finished. - NOT MEASURED locally (exit 3, prerequisite not met): `pnpm check:dual-build-cjs-loads` needs every package's `dist/` (39 are not built in this tree), and `pnpm check:type-check-debt`'s closure build never got the shared verify lock. CI's `Lint & Repo Gates` runs both. - `pnpm lint` (repo-wide, `eslint . --no-inline-config`): exit 0, no output. - Full dogfood battery (owed for the `(narrowing)` arm): running on `d151a987` under the verify lock. Its VERDICT line and counts go in the card's `os-dev-report`. ## Claim race on the card Two `Claim:` comments landed 88 s apart: this seat's 6407745341 (03:45:06Z) and `domain:server` seat 1's 6407761859 (03:46:34Z, read against a stale "unassigned" card). Seat 1 withdrew in 6407776655, and the seat's arbitration is 6407784753. This PR is the 6407745341 claim's. Seat 1's thread made no edit and opens no PR. ## Acceptance notes - **Admin import-users is not sized to the range** (noted, not filed: reach not measured live). With `passwordPolicy: 'temporary'`, `admin-import-users.ts` calls `generateTemporaryPassword()` with no length, and its deps carry no auth context. On a deployment whose `minPasswordLength` is above 16, a temporary-password import would save 16-character credentials below the minimum, the same way create-user did, because better-auth's `createUser` checks only the maximum. This is the same defect class, but it is in another file outside this claim's surface. Dedupe words: import-users temporary password minPasswordLength, generateTemporaryPassword length import. - **Two codes for one policy.** The admin doors answer `INVALID_REQUEST` (set-user-password's existing code, kept per the triage route "with the same code"), while better-auth's sign-up and change-password answer `PASSWORD_TOO_SHORT` / `PASSWORD_TOO_LONG`. Aligning them would change a wire answer, so it is out of scope here. - On both admin doors the complexity check still runs before the length check, as on set-user-password before. A short password that also fails complexity gets the complexity refusal. --- _Generated by [Claude Code](https://claude.ai/code/session_01Berf7f8pR4mCUBQwMPXUbV)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 073f08b commit 1d2a246

3 files changed

Lines changed: 192 additions & 33 deletions

File tree

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
'@objectstack/plugin-auth': patch
3+
---
4+
5+
fix(plugin-auth)!: admin create-user refuses a password outside the deployment's length range, as set-user-password does
6+
7+
`POST /api/v1/auth/admin/create-user` saved an explicit password of any length up to the maximum: better-auth's own `createUser` checks only `maxPasswordLength`, never `minPasswordLength`. An administrator could create a user with a 1-character password and `mustChangePassword: false`, and the user signed in with it, while `POST /api/v1/auth/admin/set-user-password` and the user's own change-password refuse the same value. Both admin doors now resolve a password through one check of the deployment's `minPasswordLength` / `maxPasswordLength` (the `auth.password_min_length` / `password_max_length` settings, ADR-0069):
8+
9+
- **An explicit password outside the range** is refused with `400 INVALID_REQUEST`, "Password must be at least N characters" (or "at most N"), the answer set-user-password already gave. No new error code. The refusal comes before anything is written: no account, no membership, no permission-set grant, no audit row.
10+
- **A generated password** (`generatePassword: true` with no password typed) is 16 characters, raised to the minimum when the minimum is above 16 and capped at the maximum when the maximum is below 16. Before, it was always 16, so on a deployment with a minimum above 16, set-user-password refused the password it had just generated, and create-user saved it below the minimum.
11+
12+
A password inside the range is applied exactly as before.
13+
14+
Clause-②: no (narrowing) — admin create-user refuses one request shape it used to run: an explicit `password` shorter than the deployment's `minPasswordLength` (one longer than `maxPasswordLength` was already refused by better-auth, now with set-user-password's code). Nothing is widened.
15+
16+
Metadata-protocol: none (an admin REST door's handling of a request body; nothing a metadata author writes changes)
17+
18+
<!-- adr-0087: not-required (no-migration-prescription) No metadata moves: no spec key, authorable spelling, object definition, stored row or stored shape is added, removed, renamed or re-shaped, and no exported declaration is removed or narrowed, so there is nothing for `objectstack migrate meta` to rewrite. What changes is how the admin create-user door handles a request body's explicit password: a value outside the deployment's configured length range is refused, as the set-user-password door already refused it. The remedy is a request edit (send a password inside the range, or `generatePassword: true`), never a rewrite of a package source or a stored row. The other categories are closed on facts: the package publishes (not unpublished); no ADR-0087 id is named or touched (not registered or already-registered); and nothing exported is removed or narrowed (not runtime-interface-only, type-surface-only or runtime-export-retired). -->
19+
20+
**BREAKING** for a caller that created users with passwords shorter than the deployment's minimum, or that matched the error code of a too-long one.
21+
22+
- **Before.** `POST /api/v1/auth/admin/create-user` with `password: 'a'` answered `200` and saved the password. A password longer than `maxPasswordLength` answered `400 PASSWORD_TOO_LONG`.
23+
- **Now.** The short one answers `400 INVALID_REQUEST`, "Password must be at least 8 characters" (the deployment's minimum), and creates no user. The too-long one answers `400 INVALID_REQUEST`, "Password must be at most 128 characters" (the deployment's maximum), set-user-password's answer.
24+
- **Fix.** Send a password inside the deployment's `minPasswordLength` / `maxPasswordLength` range, or send `generatePassword: true` and hand the user the returned temporary password. A client that matched `PASSWORD_TOO_LONG` on this door matches `INVALID_REQUEST` instead.

‎packages/plugins/plugin-auth/src/admin-user-endpoints.test.ts‎

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,108 @@ describe('Setup dialogs: one password rule (sys_user create_user / set_user_pass
835835
}
836836
});
837837

838+
// ── One length range for every admin door (#25824) ────────────────────────
839+
//
840+
// better-auth's own `createUser` checks only the maximum length, so
841+
// create-user saved a 1-character password (and the user signed in with it)
842+
// while set-user-password and the user's own change-password refuse it. Both
843+
// admin doors now hold the deployment's `minPasswordLength` /
844+
// `maxPasswordLength` in the one place they resolve a password, with the same
845+
// refusal, before anything is written — and a generated password is sized to
846+
// the range instead of falling outside it.
847+
describe('admin doors: one password length range (create-user / set-user-password)', () => {
848+
const DOORS = [
849+
{
850+
door: 'create-user',
851+
explicitKey: 'password',
852+
base: { email: 'x@example.com', name: 's', mustChangePassword: false },
853+
run: (m: ReturnType<typeof makeDeps>, body: Record<string, unknown>) =>
854+
runAdminCreateUser(m.deps, makeRequest(body), ACTOR),
855+
appliedPassword: (m: ReturnType<typeof makeDeps>) =>
856+
callsOf(m.createUser).map((c) => (c[0] as unknown as { body: { password: string } }).body.password),
857+
},
858+
{
859+
door: 'set-user-password',
860+
explicitKey: 'newPassword',
861+
base: { userId: 'user-9', mustChangePassword: false },
862+
run: (m: ReturnType<typeof makeDeps>, body: Record<string, unknown>) =>
863+
runAdminSetUserPassword(m.deps, makeRequest(body), ACTOR),
864+
appliedPassword: (m: ReturnType<typeof makeDeps>) =>
865+
callsOf(m.authCtx.password.hash).map((c) => c[0]),
866+
},
867+
] as const;
868+
869+
for (const d of DOORS) {
870+
for (const [shape, password, message] of [
871+
['a 1-character password (the reported repro)', 'a', 'Password must be at least 8 characters'],
872+
['a password one short of the minimum', 'Abcde1!', 'Password must be at least 8 characters'],
873+
['a password one past the maximum', `Ab1!${'x'.repeat(125)}`, 'Password must be at most 128 characters'],
874+
] as const) {
875+
it(`${d.door}: refuses ${shape} with INVALID_REQUEST, and writes nothing`, async () => {
876+
const m = makeDeps();
877+
const res = await d.run(m, { ...d.base, [d.explicitKey]: password });
878+
expect(res.status).toBe(400);
879+
expect(res.body.error?.code).toBe('INVALID_REQUEST');
880+
expect(res.body.error?.message).toBe(message);
881+
expect(m.createUser).not.toHaveBeenCalled();
882+
expect(m.authCtx.password.hash).not.toHaveBeenCalled();
883+
expect(m.authCtx.internalAdapter.updatePassword).not.toHaveBeenCalled();
884+
expect(m.authCtx.internalAdapter.createAccount).not.toHaveBeenCalled();
885+
expect(m.engineUpdate).not.toHaveBeenCalled();
886+
expect(m.engineCreate).not.toHaveBeenCalled();
887+
});
888+
}
889+
890+
for (const [shape, password] of [
891+
['exactly the minimum', 'Abcdef1!'],
892+
['exactly the maximum', `Ab1!${'x'.repeat(124)}`],
893+
] as const) {
894+
it(`${d.door}: applies a password of ${shape}`, async () => {
895+
const m = makeDeps();
896+
const res = await d.run(m, { ...d.base, [d.explicitKey]: password });
897+
expect(res.status).toBe(200);
898+
expect(d.appliedPassword(m)).toEqual([password]);
899+
});
900+
}
901+
902+
it(`${d.door}: reads the deployment's range, not a built-in 8`, async () => {
903+
const m = makeDeps();
904+
m.authCtx.password.config = { minPasswordLength: 12, maxPasswordLength: 128 };
905+
const res = await d.run(m, { ...d.base, [d.explicitKey]: 'Explicit1!' });
906+
expect(res.status).toBe(400);
907+
expect(res.body.error?.message).toBe('Password must be at least 12 characters');
908+
expect(d.appliedPassword(m)).toEqual([]);
909+
});
910+
911+
for (const [shape, config, length] of [
912+
['raised to a minimum above 16', { minPasswordLength: 24, maxPasswordLength: 128 }, 24],
913+
['capped at a maximum below 16', { minPasswordLength: 8, maxPasswordLength: 14 }, 14],
914+
] as const) {
915+
it(`${d.door}: a generated password is sized to the range (${shape})`, async () => {
916+
const m = makeDeps();
917+
m.authCtx.password.config = { ...config };
918+
const res = await d.run(m, { ...d.base, generatePassword: true });
919+
expect(res.status).toBe(200);
920+
const temp = (res.body.data as any).temporaryPassword as string;
921+
expect(temp).toHaveLength(length);
922+
expect(d.appliedPassword(m)).toEqual([temp]);
923+
});
924+
}
925+
}
926+
927+
it('create-user: a range that cannot be read refuses, and no account is created', async () => {
928+
const m = makeDeps({
929+
getAuthContext: vi.fn(async () => {
930+
throw new Error('auth context unavailable');
931+
}),
932+
});
933+
const res = await runAdminCreateUser(m.deps, makeRequest({ email: 'x@example.com', password: 'Explicit1!' }), ACTOR);
934+
expect(res.status).toBe(500);
935+
expect(res.body.error?.code).toBe('INTERNAL_ERROR');
936+
expect(m.createUser).not.toHaveBeenCalled();
937+
});
938+
});
939+
838940
// ── The ledger row records the admin's decisions, never a field value ──────
839941
//
840942
// `sys_audit_log.metadata` is free text. The ledger's read side narrows the

‎packages/plugins/plugin-auth/src/admin-user-endpoints.ts‎

Lines changed: 66 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -238,10 +238,11 @@ const SYSTEM_CTX = { isSystem: true, positions: [], permissions: [] };
238238

239239
// ── Temporary password generation ────────────────────────────────────────
240240
//
241-
// Must satisfy the ADR-0069 class-mix policy (lower + upper + digit + symbol)
242-
// and the better-auth min length regardless of deployment config, so one
243-
// guaranteed character per class + random fill. Ambiguous glyphs (O/0, l/1)
244-
// are excluded — these passwords are read off a dialog and typed once.
241+
// Must satisfy the ADR-0069 class-mix policy (lower + upper + digit + symbol),
242+
// so one guaranteed character per class + random fill. Never shorter than 12;
243+
// a caller holding the deployment's length range sizes it to that range (see
244+
// `temporaryPasswordLength`). Ambiguous glyphs (O/0, l/1) are excluded — these
245+
// passwords are read off a dialog and typed once.
245246
const LOWER = 'abcdefghijkmnopqrstuvwxyz';
246247
const UPPER = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
247248
const DIGIT = '23456789';
@@ -317,42 +318,71 @@ export function isLikelyEmail(value: string): boolean {
317318
return dot > 0 && dot < domain.length - 1;
318319
}
319320

321+
/**
322+
* [#25824] The deployment's password length range, as better-auth resolved it:
323+
* `emailAndPassword.minPasswordLength` / `maxPasswordLength`, which ADR-0069
324+
* maps the `auth.password_min_length` / `password_max_length` settings onto.
325+
*/
326+
type PasswordLengthPolicy = AuthContextLike['password']['config'];
327+
328+
/** A generated password's length: 16, raised to the range's minimum and capped at its maximum. */
329+
function temporaryPasswordLength({ minPasswordLength, maxPasswordLength }: PasswordLengthPolicy): number {
330+
return Math.min(Math.max(16, minPasswordLength), maxPasswordLength);
331+
}
332+
320333
/**
321334
* Resolve the password for a create/set request: an explicit `password` /
322335
* `newPassword`, or a generated temporary one when `generatePassword` is true.
323336
* An explicit non-empty password always wins over `generatePassword` — the
324337
* console action dialog defaults `generatePassword: true` and labels the
325338
* password input "leave empty to generate", so typing a password is an
326339
* unambiguous override, not a conflict.
340+
*
341+
* [#25824] Every admin door that sets a credential resolves it here, so this
342+
* is where the length range is held, for an explicit and a generated password
343+
* alike: better-auth's own `createUser` checks only the maximum, and
344+
* create-user used to save a 1-character password that set-user-password and
345+
* change-password refuse.
327346
* Returns an EndpointResult on validation failure.
328347
*/
329348
async function resolvePassword(
330349
deps: Pick<AdminUserEndpointDeps, 'assertPasswordComplexity'>,
331350
body: Record<string, unknown>,
332351
explicitKey: 'password' | 'newPassword',
352+
lengthPolicy: PasswordLengthPolicy,
333353
): Promise<{ password: string; generated: boolean } | EndpointResult> {
334354
const explicit = body[explicitKey];
335355
const generate = body.generatePassword === true;
356+
let resolved: { password: string; generated: boolean };
336357
if (typeof explicit !== 'string' || explicit.length === 0) {
337-
if (generate) return { password: generateTemporaryPassword(), generated: true };
338-
return badRequest(`${explicitKey} is required (or set generatePassword: true)`);
339-
}
340-
try {
341-
await deps.assertPasswordComplexity(explicit);
342-
} catch (error) {
343-
const e = error as { code?: string; message?: string } | null;
344-
return {
345-
status: 400,
346-
body: {
347-
success: false,
348-
error: {
349-
code: e?.code ?? 'PASSWORD_POLICY_VIOLATION',
350-
message: e?.message ?? 'Password does not meet the complexity policy',
358+
if (!generate) return badRequest(`${explicitKey} is required (or set generatePassword: true)`);
359+
resolved = { password: generateTemporaryPassword(temporaryPasswordLength(lengthPolicy)), generated: true };
360+
} else {
361+
try {
362+
await deps.assertPasswordComplexity(explicit);
363+
} catch (error) {
364+
const e = error as { code?: string; message?: string } | null;
365+
return {
366+
status: 400,
367+
body: {
368+
success: false,
369+
error: {
370+
code: e?.code ?? 'PASSWORD_POLICY_VIOLATION',
371+
message: e?.message ?? 'Password does not meet the complexity policy',
372+
},
351373
},
352-
},
353-
};
374+
};
375+
}
376+
resolved = { password: explicit, generated: false };
377+
}
378+
const { minPasswordLength, maxPasswordLength } = lengthPolicy;
379+
if (resolved.password.length < minPasswordLength) {
380+
return badRequest(`Password must be at least ${minPasswordLength} characters`);
381+
}
382+
if (resolved.password.length > maxPasswordLength) {
383+
return badRequest(`Password must be at most ${maxPasswordLength} characters`);
354384
}
355-
return { password: explicit, generated: false };
385+
return resolved;
356386
}
357387

358388
/**
@@ -688,7 +718,14 @@ export async function runAdminCreateUser(
688718
const permissionSet =
689719
typeof rawPermissionSet === 'string' && rawPermissionSet.trim().length > 0 ? rawPermissionSet.trim() : undefined;
690720

691-
const resolved = await resolvePassword(deps, body, 'password');
721+
// [#25824] The length range is judged here, before any account exists.
722+
let lengthPolicy: PasswordLengthPolicy;
723+
try {
724+
lengthPolicy = (await deps.getAuthContext()).password.config;
725+
} catch (error) {
726+
return mapAuthApiError(error, 'create-user failed');
727+
}
728+
const resolved = await resolvePassword(deps, body, 'password', lengthPolicy);
692729
if ('status' in resolved) return resolved;
693730

694731
// ADR-0093 D3 amendment — in multi-org mode the new user joins the
@@ -1026,20 +1063,16 @@ export async function runAdminSetUserPassword(
10261063
// malformed value keeps the safe default.
10271064
const revokeSessionsAndKeys = body.revokeSessionsAndKeys !== false;
10281065

1029-
const resolved = await resolvePassword(deps, body, 'newPassword');
1066+
let authCtx: AuthContextLike;
1067+
try {
1068+
authCtx = await deps.getAuthContext();
1069+
} catch (error) {
1070+
return mapAuthApiError(error, 'set-user-password failed');
1071+
}
1072+
const resolved = await resolvePassword(deps, body, 'newPassword', authCtx.password.config);
10301073
if ('status' in resolved) return resolved;
10311074

10321075
try {
1033-
const authCtx = await deps.getAuthContext();
1034-
1035-
const { minPasswordLength, maxPasswordLength } = authCtx.password.config;
1036-
if (resolved.password.length < minPasswordLength) {
1037-
return badRequest(`Password must be at least ${minPasswordLength} characters`);
1038-
}
1039-
if (resolved.password.length > maxPasswordLength) {
1040-
return badRequest(`Password must be at most ${maxPasswordLength} characters`);
1041-
}
1042-
10431076
if (!(await authCtx.internalAdapter.findUserById(userId))) {
10441077
return { status: 404, body: { success: false, error: { code: 'RESOURCE_NOT_FOUND', message: 'User not found' } } };
10451078
}

0 commit comments

Comments
 (0)