You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// in a service wired to `auth.admin`const{ admin }=service.load();const{ user }=awaitadmin.setPassword({ userId,newPassword: 'a-new-long-passphrase'});// user === null → no such account. Otherwise the password is replaced and every session of that account is signed out.
Today this does not compile: the admin port of @prisma/composer-prisma-cloud/auth has no operation that sets a password.
Published: @prisma/composer-prisma-cloud 0.21.0, 0.22.0, 0.23.0, 0.24.0 and 0.25.0 (the newest, latest tag on 2026-09-30) were each unpacked and checked. None has a password operation on the admin port.
Better Auth: 1.6.24, the version the auth package pins.
All paths below are relative to the repository root at that commit.
Summary
The auth module can create an account with a password (admin.createUser), but nothing lets an operator change the password of an existing account. An application whose users cannot receive a reset email therefore has no supported way to let a person back in after they forget their password. We ask for one new operation on the admin port, admin.setPassword, built the same way as createUser: server to server, authorized by wiring, writing Better Auth's own rows with Better Auth's own hasher.
This hurts every application that runs the module without real email delivery, and any operator who needs to reset a password faster than an email round trip. The concrete case is Asks, an internal Prisma application described below.
Background
How the auth module exposes admin operations today
The module is packages/1-prisma-cloud/2-shared-modules/auth (published inside @prisma/composer-prisma-cloud as the ./auth export). It exposes three ports: api (Better Auth's public HTTP surface), session (rpc) and admin (rpc).
The admin contract is authAdminContract in src/contract.ts, lines 197 to 252. Its operations are findUser, listUsers, listSessions, revokeSession, revokeUserSessions, banUser, unbanUser, createUser, setEmailVerified and removeUser. The last three look like this:
The admin handlers do not call Better Auth's admin plugin. They write the auth database directly through an AuthStore, because the ports are authorized by wiring (a service key), not by a Better Auth admin session:
src/execution/auth-entrypoint.ts lines 27 to 31 say the same and wire the handlers into serve(). createUser already hashes a password with Better Auth's own hasher (hashPassword from better-auth/crypto, src/handlers.ts lines 10 and 140 to 155) and inserts the credential account row (src/pg-auth-store.ts lines 228 to 255). banUser already shows the pattern for "change a row and delete the user's sessions in one transaction" (src/pg-auth-store.ts lines 194 to 214).
The README describes the admin port as "the tier-1 admin path" and says to wire it only into a back office (README.md lines 28 to 37). It documents provisioning (README.md line 94 onward) and deletion (line 140 onward), and says a deployed stack's rpc ports are reachable only by consumers in its graph (line 121).
The Better Auth configuration is src/auth-options.ts. It enables email and password with requireEmailVerification: true, sends reset mail through the email module (sendResetPassword, line 125), sets revokeSessionsOnPasswordReset: true (line 126), enables self-service /delete-user (line 134), and installs Better Auth's admin() plugin (line 157).
How Asks uses the module
Asks is an internal Prisma application that runs on Composer with @prisma/composer-prisma-cloud 0.21.0 (its pnpm-lock.yaml, and "@prisma/composer-prisma-cloud": "0.21.0" in apps/api/package.json, apps/web/package.json and deploy/package.json). Its wiring, from deploy/module.ts:
Sign-up is closed. Every account is made by an operator through the Asks API, which calls admin.createUser over rpc(authAdminContract) (apps/api/service.ts line 17). Asks stores the auth module's user id in its own database as the account an authorization belongs to.
The web app forwards every path under /api/auth to the module with authProxy(authApi) (apps/web/server.ts lines 6, 81 and 99). Signing out and changing your own password are server actions that post to /api/auth/sign-out and /api/auth/change-password with the browser's cookie and Origin (apps/web/app/server/auth-surface.ts, signedInAuthPost). So a signed-in person can change their own password today.
The very first account of an environment is written by scripts/grant-first-authorization.mjs, which connects to the auth database with SQL and carries its own copy of Better Auth's scrypt hash. It runs once per environment, by a person, only when the account does not exist yet. It is a bootstrap, not a reset tool.
Why reset mail is off
Asks production has no outgoing mail. It sets the email module's delivery mode to none (deploy/.env.example: ASKS_EMAIL_DELIVERY_MODE=none). Its apps/api/README.md says: "Set the mode to none and nothing is ever delivered, which is what production runs". Better Auth's /request-password-reset still answers, but the email never arrives. So a person who forgets their password cannot reset it themselves, and an operator has no supported way to set it for them.
Getting the code
From CONTRIBUTING.md and the root package.json (Node 24.16.0 and bun 1.3.13 from .tool-versions; pnpm 10.27.0 from packageManager). Note that the clone URL in CONTRIBUTING.md still reads prisma/compose.git and cd app; the commands below use the current repository name.
git clone https://github.com/prisma/composer.git
cd composer
git checkout edaf7b272d7a4d5f1563eb25f5de0072af2b5b10 # or current main
corepack enable
pnpm install --frozen-lockfile
pnpm build
# The auth package is the workspace package @internal/auth
pnpm --filter @internal/auth test# bun test src
pnpm --filter @internal/auth typecheck # tsc --noEmit, which also checks src/__tests__/contract.test-d.ts
pnpm lint
The integration tests (*.integration.test.ts) need Postgres. src/__tests__/postgres-harness.ts uses STATE_TEST_DATABASE_URL when it is set, otherwise starts a throwaway cluster with initdb and pg_ctl from PATH, and otherwise skips locally (it fails on CI instead).
Reproduction
A consumer project with only the published package (npm install @prisma/composer-prisma-cloud@0.25.0 typescript@6.0.3).
What it does. Hashes newPassword with Better Auth's own hasher and stores it on the user's credential account row. When the user has no credential account (a magic-link-only account), it creates one, exactly as createUser does and as Better Auth's own setUserPassword does. In the same transaction it deletes every session of the user. It sends no mail.
Authorization. Only through the admin rpc port, which serve() already checks against the service key, so only services the root wires to admin can call it. It is not added to the public /api/auth surface and needs no Better Auth role.
Result.{ user } with the updated record, or { user: null } when no user has that id. This matches setEmailVerified, and means a retried call is not an error.
Errors. A newPassword shorter than 8 or longer than 128 characters, or a missing field, is refused by the contract input: a 400 at the rpc boundary, thrown by a typed client. These are the same bounds createUser and Better Auth's sign-up apply. A database failure is a 500, as for every other handler.
Ban state is untouched. A banned user stays banned; setting a password does not unban.
Design questions, with recommendations
Name. Recommend setPassword. It reads like the existing setEmailVerified and like Better Auth's setUserPassword, and "set" says the operator chooses the value, which "reset" (a flow with a token and an email) does not.
Account id or email. Recommend userId, and not an email. Every other admin operation that changes a user takes userId, and a caller who has only an email resolves it with findUser({ email }) first. The word "account" should be avoided in the field name: in Better Auth's schema an "account" is a row in the account table (one per sign-in method), and accountId is a column there, so accountId would be ambiguous.
Whether it signs the user out everywhere. Recommend yes, always, in the same transaction, as banUser does. The module already sets revokeSessionsOnPasswordReset: true for the email reset, and the usual reason for an operator reset is that the old password may be known to someone else. Better Auth's own setUserPassword does not revoke sessions (Better Auth 1.6.24, dist/plugins/admin/routes.mjs lines 818 to 846), so this is a deliberate difference and the README should say so. If the maintainers want it optional, a revokeSessions?: boolean defaulting to true is the smallest addition; we do not need it.
Limited to the server-side admin credential. Recommend yes: rpc only, on the admin port. It must not become a Better Auth route on the public surface, and it must not require a Better Auth admin role. This follows the module's existing rule that "ports authorize via wiring, never via Better Auth admin sessions".
Audit logging. The module keeps no audit log for any admin operation today. Recommend one structured log line per call with the operation name and userId (never the password), and leave a durable audit record to the calling application, which knows which operator acted. Also check that nothing logs the request body: the rpc idempotency store keeps the answer, not the input, and the answer here holds no secret.
Implementation outline
src/contract.ts: add setPassword to authAdminContract with the input and output above.
src/auth-store.ts: add setPassword(userId: string, credential: { id: string; passwordHash: string }): Promise<UserRecord | null> to AuthStore, documented as "replaces the credential password or inserts the credential row, and deletes all the user's sessions, atomically; null when the user is absent".
src/pg-auth-store.ts: implement it in one this.sql.begin transaction. Lock the user row (select ... for update) and return null when it is absent. Update password and updatedAt on the account row where "userId" = $1 and "providerId" = 'credential'. If no row was updated, insert one the way createUser does ("accountId" is the user id). Delete the user's sessions. Update the user's updatedAt and return the record. The lock matters because the account table has no unique index on ("userId", "providerId") (src/pack/contract.prisma lines 51 to 69), so two concurrent calls could otherwise both insert.
src/handlers.ts: add setPassword to AdminHandlers; hash with hashPassword from better-auth/crypto, mint the row id with the existing generateId(), and call the store.
apps-facing surface: nothing else. The port, wiring and public exports already carry every admin operation.
Optionally add a /admin/set-password route to examples/auth/src/ops/app.ts and a step to examples/auth/scripts/smoke.ts, so the deployed example exercises it.
Acceptance criteria
Tests to add, all in packages/1-prisma-cloud/2-shared-modules/auth/src/__tests__/:
contract.test.ts, in describe('authAdminContract — the provisioning methods'): the input needs userId and newPassword; 7 and 129 characters are refused; 8 and 128 are accepted; the output accepts { user: null }.
contract.test-d.ts, next to "the admin client types createUser and setEmailVerified": Parameters<Admin['setPassword']>[0] equals { userId: string; newPassword: string } and the result is { user: UserRecord | null }.
handlers.test.ts: the store receives a hash that is not the plaintext and that Better Auth's verifyPassword accepts; null from the store comes back as { user: null } without a throw.
pg-auth-store.integration.test.ts, a new describe('setPassword'): replaces the hash of an existing credential row and leaves exactly one such row; inserts a credential row for a user without one; deletes all of that user's sessions and none of another user's; returns null and writes nothing for an unknown id; leaves the ban columns unchanged.
local-server.integration.test.ts: create a user with password A through admin.createUser, sign in through the real /sign-in/email, call admin.setPassword with password B, then check that the old session no longer resolves through session.getSession, sign-in with A is refused, and sign-in with B succeeds. Also: a user created without a password can sign in with a password after setPassword.
Commands that must pass: pnpm build && pnpm typecheck && pnpm lint && pnpm test (the gate in CONTRIBUTING.md), with the auth integration tests actually running against Postgres rather than skipping.
Documents to update:
packages/1-prisma-cloud/2-shared-modules/auth/README.md: add setPassword to the list of admin operations (lines 28 to 35), and a short section "Setting a password" after "Provisioning accounts", with the example at the top of this issue, the session revocation, the difference from Better Auth's setUserPassword, and that it sends no mail.
skills/prisma-composer-core-concepts/SKILL.md line 421, the auth row, which lists the admin operations a consumer uses.
The auth module spec under .drive/projects/auth-module/, if it is still the source of truth for the contract.
Alternatives considered
These are the three other ways Asks could let an operator reset a password today. Each is worse than a supported admin operation.
Remove the account and create it again.admin.removeUser (0.22.0 and later) followed by admin.createUser with the new password. It uses only supported operations, but the new account has a new user id. The application must then repoint its own rows that name the old id, in a second database, so the change is not atomic: a failure between the two steps leaves a person with no account or with an account nothing points at. Upgrading to a version with removeUser also turns on Better Auth's self-service /delete-user (src/auth-options.ts line 134), which an application proxying all of /api/auth then exposes unless it blocks that path.
Update the hash with SQL. Write a new scrypt hash into auth.account.password directly, as the Asks bootstrap script does for the first account. It works, but the application's API has no credential for the auth database (only the module's own service does), it copies Better Auth's hash format into application code where it silently breaks if Better Auth changes it, and it bypasses the module that owns those tables.
Use Better Auth's admin plugin. The module installs admin() (src/auth-options.ts line 157), so POST /api/auth/admin/set-user-password exists on the public surface. It requires a signed-in session whose user has the admin role, is in adminUserIds, or has a custom role with the user:set-password permission (Better Auth admin plugin documentation; the check is in dist/plugins/admin/routes.mjs lines 819 to 824 of Better Auth 1.6.24). The module has no operation that sets a role or passes admin plugin options, so the role would be written into the user table by hand. With the default admin role, that person's browser session could also ban users, remove users, set roles and impersonate users through the application's proxy. A custom role with only user:set-password narrows that, but it still needs module configuration that does not exist, a hand-written role, and a human browser session for what is a server-to-server operation. It also contradicts the module's own design, in which admin ports are authorized by wiring and not by Better Auth admin sessions.
Out of scope
Self-service password reset by email (Better Auth's /request-password-reset and /reset-password). It already exists and works wherever the email module delivers mail.
Changing your own password while signed in (Better Auth's /change-password). It already works through authProxy().
An admin web UI, role management, and exposing more of Better Auth's admin plugin.
At a glance
Today this does not compile: the
adminport of@prisma/composer-prisma-cloud/authhas no operation that sets a password.The commit and versions these paths refer to
prisma/composerat commitedaf7b272d7a4d5f1563eb25f5de0072af2b5b10(main, 2026-09-30, "Merge pull request fix(cli): report the engine's failure cause with DEPLOY.ENGINE_FAILED #326"). The auth package there is at version 0.25.0.@prisma/composer-prisma-cloud0.21.0, 0.22.0, 0.23.0, 0.24.0 and 0.25.0 (the newest,latesttag on 2026-09-30) were each unpacked and checked. None has a password operation on the admin port.All paths below are relative to the repository root at that commit.
Summary
The auth module can create an account with a password (
admin.createUser), but nothing lets an operator change the password of an existing account. An application whose users cannot receive a reset email therefore has no supported way to let a person back in after they forget their password. We ask for one new operation on theadminport,admin.setPassword, built the same way ascreateUser: server to server, authorized by wiring, writing Better Auth's own rows with Better Auth's own hasher.This hurts every application that runs the module without real email delivery, and any operator who needs to reset a password faster than an email round trip. The concrete case is Asks, an internal Prisma application described below.
Background
How the auth module exposes admin operations today
The module is
packages/1-prisma-cloud/2-shared-modules/auth(published inside@prisma/composer-prisma-cloudas the./authexport). It exposes three ports:api(Better Auth's public HTTP surface),session(rpc) andadmin(rpc).The
admincontract isauthAdminContractinsrc/contract.ts, lines 197 to 252. Its operations arefindUser,listUsers,listSessions,revokeSession,revokeUserSessions,banUser,unbanUser,createUser,setEmailVerifiedandremoveUser. The last three look like this:The admin handlers do not call Better Auth's admin plugin. They write the auth database directly through an
AuthStore, because the ports are authorized by wiring (a service key), not by a Better Auth admin session:src/execution/auth-entrypoint.tslines 27 to 31 say the same and wire the handlers intoserve().createUseralready hashes a password with Better Auth's own hasher (hashPasswordfrombetter-auth/crypto,src/handlers.tslines 10 and 140 to 155) and inserts thecredentialaccount row (src/pg-auth-store.tslines 228 to 255).banUseralready shows the pattern for "change a row and delete the user's sessions in one transaction" (src/pg-auth-store.tslines 194 to 214).The README describes the admin port as "the tier-1 admin path" and says to wire it only into a back office (
README.mdlines 28 to 37). It documents provisioning (README.mdline 94 onward) and deletion (line 140 onward), and says a deployed stack's rpc ports are reachable only by consumers in its graph (line 121).The Better Auth configuration is
src/auth-options.ts. It enables email and password withrequireEmailVerification: true, sends reset mail through the email module (sendResetPassword, line 125), setsrevokeSessionsOnPasswordReset: true(line 126), enables self-service/delete-user(line 134), and installs Better Auth'sadmin()plugin (line 157).How Asks uses the module
Asks is an internal Prisma application that runs on Composer with
@prisma/composer-prisma-cloud0.21.0 (itspnpm-lock.yaml, and"@prisma/composer-prisma-cloud": "0.21.0"inapps/api/package.json,apps/web/package.jsonanddeploy/package.json). Its wiring, fromdeploy/module.ts:admin.createUseroverrpc(authAdminContract)(apps/api/service.tsline 17). Asks stores the auth module's user id in its own database as the account an authorization belongs to./api/authto the module withauthProxy(authApi)(apps/web/server.tslines 6, 81 and 99). Signing out and changing your own password are server actions that post to/api/auth/sign-outand/api/auth/change-passwordwith the browser's cookie andOrigin(apps/web/app/server/auth-surface.ts,signedInAuthPost). So a signed-in person can change their own password today.scripts/grant-first-authorization.mjs, which connects to the auth database with SQL and carries its own copy of Better Auth's scrypt hash. It runs once per environment, by a person, only when the account does not exist yet. It is a bootstrap, not a reset tool.Why reset mail is off
Asks production has no outgoing mail. It sets the email module's delivery mode to
none(deploy/.env.example:ASKS_EMAIL_DELIVERY_MODE=none). Itsapps/api/README.mdsays: "Set the mode tononeand nothing is ever delivered, which is what production runs". Better Auth's/request-password-resetstill answers, but the email never arrives. So a person who forgets their password cannot reset it themselves, and an operator has no supported way to set it for them.Getting the code
From
CONTRIBUTING.mdand the rootpackage.json(Node 24.16.0 and bun 1.3.13 from.tool-versions; pnpm 10.27.0 frompackageManager). Note that the clone URL inCONTRIBUTING.mdstill readsprisma/compose.gitandcd app; the commands below use the current repository name.The integration tests (
*.integration.test.ts) need Postgres.src/__tests__/postgres-harness.tsusesSTATE_TEST_DATABASE_URLwhen it is set, otherwise starts a throwaway cluster withinitdbandpg_ctlfromPATH, and otherwise skips locally (it fails on CI instead).Reproduction
A consumer project with only the published package (
npm install @prisma/composer-prisma-cloud@0.25.0 typescript@6.0.3).At runtime, the contract's method list:
At the type level, the call a consumer would write:
The same list of operations is in the dist of 0.21.0 (without
removeUser), 0.22.0, 0.23.0 and 0.24.0.Expected behaviour
A new operation on
authAdminContract:newPasswordwith Better Auth's own hasher and stores it on the user'scredentialaccount row. When the user has nocredentialaccount (a magic-link-only account), it creates one, exactly ascreateUserdoes and as Better Auth's ownsetUserPassworddoes. In the same transaction it deletes every session of the user. It sends no mail.adminrpc port, whichserve()already checks against the service key, so only services the root wires toadmincan call it. It is not added to the public/api/authsurface and needs no Better Auth role.{ user }with the updated record, or{ user: null }when no user has that id. This matchessetEmailVerified, and means a retried call is not an error.newPasswordshorter than 8 or longer than 128 characters, or a missing field, is refused by the contract input: a 400 at the rpc boundary, thrown by a typed client. These are the same boundscreateUserand Better Auth's sign-up apply. A database failure is a 500, as for every other handler.Design questions, with recommendations
setPassword. It reads like the existingsetEmailVerifiedand like Better Auth'ssetUserPassword, and "set" says the operator chooses the value, which "reset" (a flow with a token and an email) does not.userId, and not an email. Every other admin operation that changes a user takesuserId, and a caller who has only an email resolves it withfindUser({ email })first. The word "account" should be avoided in the field name: in Better Auth's schema an "account" is a row in theaccounttable (one per sign-in method), andaccountIdis a column there, soaccountIdwould be ambiguous.banUserdoes. The module already setsrevokeSessionsOnPasswordReset: truefor the email reset, and the usual reason for an operator reset is that the old password may be known to someone else. Better Auth's ownsetUserPassworddoes not revoke sessions (Better Auth 1.6.24,dist/plugins/admin/routes.mjslines 818 to 846), so this is a deliberate difference and the README should say so. If the maintainers want it optional, arevokeSessions?: booleandefaulting totrueis the smallest addition; we do not need it.adminport. It must not become a Better Auth route on the public surface, and it must not require a Better Authadminrole. This follows the module's existing rule that "ports authorize via wiring, never via Better Auth admin sessions".userId(never the password), and leave a durable audit record to the calling application, which knows which operator acted. Also check that nothing logs the request body: the rpc idempotency store keeps the answer, not the input, and the answer here holds no secret.Implementation outline
src/contract.ts: addsetPasswordtoauthAdminContractwith the input and output above.src/auth-store.ts: addsetPassword(userId: string, credential: { id: string; passwordHash: string }): Promise<UserRecord | null>toAuthStore, documented as "replaces thecredentialpassword or inserts thecredentialrow, and deletes all the user's sessions, atomically;nullwhen the user is absent".src/pg-auth-store.ts: implement it in onethis.sql.begintransaction. Lock the user row (select ... for update) and returnnullwhen it is absent. UpdatepasswordandupdatedAton the account row where"userId" = $1 and "providerId" = 'credential'. If no row was updated, insert one the waycreateUserdoes ("accountId"is the user id). Delete the user's sessions. Update the user'supdatedAtand return the record. The lock matters because theaccounttable has no unique index on("userId", "providerId")(src/pack/contract.prismalines 51 to 69), so two concurrent calls could otherwise both insert.src/handlers.ts: addsetPasswordtoAdminHandlers; hash withhashPasswordfrombetter-auth/crypto, mint the row id with the existinggenerateId(), and call the store.apps-facing surface: nothing else. The port, wiring and public exports already carry every admin operation./admin/set-passwordroute toexamples/auth/src/ops/app.tsand a step toexamples/auth/scripts/smoke.ts, so the deployed example exercises it.Acceptance criteria
Tests to add, all in
packages/1-prisma-cloud/2-shared-modules/auth/src/__tests__/:contract.test.ts, indescribe('authAdminContract — the provisioning methods'): the input needsuserIdandnewPassword; 7 and 129 characters are refused; 8 and 128 are accepted; the output accepts{ user: null }.contract.test-d.ts, next to "the admin client types createUser and setEmailVerified":Parameters<Admin['setPassword']>[0]equals{ userId: string; newPassword: string }and the result is{ user: UserRecord | null }.handlers.test.ts: the store receives a hash that is not the plaintext and that Better Auth'sverifyPasswordaccepts;nullfrom the store comes back as{ user: null }without a throw.pg-auth-store.integration.test.ts, a newdescribe('setPassword'): replaces the hash of an existingcredentialrow and leaves exactly one such row; inserts acredentialrow for a user without one; deletes all of that user's sessions and none of another user's; returnsnulland writes nothing for an unknown id; leaves the ban columns unchanged.local-server.integration.test.ts: create a user with password A throughadmin.createUser, sign in through the real/sign-in/email, calladmin.setPasswordwith password B, then check that the old session no longer resolves throughsession.getSession, sign-in with A is refused, and sign-in with B succeeds. Also: a user created without a password can sign in with a password aftersetPassword.Commands that must pass:
pnpm build && pnpm typecheck && pnpm lint && pnpm test(the gate inCONTRIBUTING.md), with the auth integration tests actually running against Postgres rather than skipping.Documents to update:
packages/1-prisma-cloud/2-shared-modules/auth/README.md: addsetPasswordto the list of admin operations (lines 28 to 35), and a short section "Setting a password" after "Provisioning accounts", with the example at the top of this issue, the session revocation, the difference from Better Auth'ssetUserPassword, and that it sends no mail.skills/prisma-composer-core-concepts/SKILL.mdline 421, theauthrow, which lists the admin operations a consumer uses..drive/projects/auth-module/, if it is still the source of truth for the contract.Alternatives considered
These are the three other ways Asks could let an operator reset a password today. Each is worse than a supported admin operation.
admin.removeUser(0.22.0 and later) followed byadmin.createUserwith the new password. It uses only supported operations, but the new account has a new user id. The application must then repoint its own rows that name the old id, in a second database, so the change is not atomic: a failure between the two steps leaves a person with no account or with an account nothing points at. Upgrading to a version withremoveUseralso turns on Better Auth's self-service/delete-user(src/auth-options.tsline 134), which an application proxying all of/api/auththen exposes unless it blocks that path.auth.account.passworddirectly, as the Asks bootstrap script does for the first account. It works, but the application's API has no credential for the auth database (only the module's own service does), it copies Better Auth's hash format into application code where it silently breaks if Better Auth changes it, and it bypasses the module that owns those tables.admin()(src/auth-options.tsline 157), soPOST /api/auth/admin/set-user-passwordexists on the public surface. It requires a signed-in session whose user has theadminrole, is inadminUserIds, or has a custom role with theuser:set-passwordpermission (Better Auth admin plugin documentation; the check is indist/plugins/admin/routes.mjslines 819 to 824 of Better Auth 1.6.24). The module has no operation that sets a role or passes admin plugin options, so the role would be written into theusertable by hand. With the defaultadminrole, that person's browser session could also ban users, remove users, set roles and impersonate users through the application's proxy. A custom role with onlyuser:set-passwordnarrows that, but it still needs module configuration that does not exist, a hand-written role, and a human browser session for what is a server-to-server operation. It also contradicts the module's own design, in which admin ports are authorized by wiring and not by Better Auth admin sessions.Out of scope
/request-password-resetand/reset-password). It already exists and works wherever the email module delivers mail./change-password). It already works throughauthProxy().🤖 Generated with Claude Code