From 45c05c2f854197f24c2f981df0fbed5251c5a5de Mon Sep 17 00:00:00 2001 From: Paulo Carinhena <63272749+paulocarinhena@users.noreply.github.com> Date: Thu, 12 Nov 2026 19:30:45 -0300 Subject: [PATCH] Evita que o admin entre na conta que acabou de criar. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit O cadastro pela tela de administração não inicia sessão, o primeiro admin só é reivindicado junto com o usuário, e o login OIDC deixa de criar conta quando o registro está fechado. Co-authored-by: Cursor --- INSTALL.md | 2 +- src/app/actions/admin.ts | 23 +++++++++++++++++++---- src/lib/auth.ts | 11 +++++++++++ src/lib/oidc.ts | 2 +- tests/oidc.test.ts | 2 +- 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 782c023..0a52613 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -228,7 +228,7 @@ to `docker-compose.yml` and run `docker compose up -d` again. | `OIDC_CLIENT_ID` / `OIDC_CLIENT_SECRET` | — | Client credentials at the provider (Authentik, Authelia, Keycloak…) | | `OIDC_PROVIDER_NAME` | `SSO` | Name on the “Sign in with …” button | | `OIDC_SCOPES` | `openid email profile` | Requested scopes | -| `OIDC_AUTO_REGISTER` | `true` | Create an account for anyone the provider authenticates; with `false`, the instance's open-registration setting applies | +| `OIDC_AUTO_REGISTER` | `false` | Create an account for anyone the provider authenticates. Off by default, so closed registration also blocks new SSO accounts until this is `true` | | `OIDC_DISABLE_PASSWORD_LOGIN` | `false` | SSO only: hides and disables password sign-in | diff --git a/src/app/actions/admin.ts b/src/app/actions/admin.ts index 2f84d0a..9e7d9e9 100644 --- a/src/app/actions/admin.ts +++ b/src/app/actions/admin.ts @@ -53,12 +53,27 @@ export async function createUserAction(input: z.input) { const parsed = userSchema.safeParse(input); if (!parsed.success) return { ok: false as const, error: actionErrorMessage(t, parsed.error) }; const { role, ...body } = parsed.data; + // Pelo adaptador, sem signUpEmail: aquele caminho cria sessão e o plugin de cookies + // gravaria essa sessão na resposta da action, no lugar da do admin. + let createdId: string | null = null; try { - // Chamada server-side (sem request): não é bloqueada pelo "cadastro fechado" e não cria sessão. - const res = await auth.api.signUpEmail({ body }); - await db.user.update({ where: { id: res.user.id }, data: { role } }); + const ctx = await auth.$context; + const email = body.email.toLowerCase(); + if (await ctx.internalAdapter.findUserByEmail(email)) return { ok: false as const, error: t("createUserFailed") }; + const created = await ctx.internalAdapter.createUser({ email, name: body.name, role }, { method: "admin" }); + if (!created) return { ok: false as const, error: t("createUserFailed") }; + createdId = created.id; + await ctx.internalAdapter.linkAccount({ + userId: created.id, + providerId: "credential", + accountId: created.id, + password: await ctx.password.hash(body.password), + }); + if (created.role !== role) await db.user.update({ where: { id: created.id }, data: { role } }); } catch (err) { - return { ok: false as const, error: err instanceof Error ? err.message : t("createUserFailed") }; + if (createdId) await db.user.delete({ where: { id: createdId } }).catch(() => {}); + console.error("[admin] createUser", err); + return { ok: false as const, error: t("createUserFailed") }; } revalidatePath("/settings/admin"); return { ok: true as const }; diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 2336ed4..b00fc5e 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -89,6 +89,17 @@ export const auth = lazyObject(() => { return { data: { ...user, role: "user" } }; } + // Um cadastro que falhou depois de reservar o e-mail não pode travar a instância nesse endereço. + if (settings.bootstrapAdminEmail) { + const owner = await db.user.findFirst({ where: { email: settings.bootstrapAdminEmail }, select: { id: true } }); + if (!owner) { + await db.appSettings.updateMany({ + where: { id: "app", bootstrapAdminEmail: settings.bootstrapAdminEmail }, + data: { bootstrapAdminEmail: null }, + }); + } + } + const claimed = await db.$executeRaw` UPDATE "app_settings" SET "bootstrapAdminEmail" = ${user.email} diff --git a/src/lib/oidc.ts b/src/lib/oidc.ts index 6e5cf5f..484d4b3 100644 --- a/src/lib/oidc.ts +++ b/src/lib/oidc.ts @@ -30,7 +30,7 @@ export function readOidcConfig(env: Env = process.env): OidcConfig | null { clientSecret: env.OIDC_CLIENT_SECRET?.trim() || undefined, name: env.OIDC_PROVIDER_NAME?.trim() || "SSO", scopes: (env.OIDC_SCOPES?.trim() || "openid email profile").split(/[\s,]+/).filter(Boolean), - autoRegister: flag(env.OIDC_AUTO_REGISTER, true), + autoRegister: flag(env.OIDC_AUTO_REGISTER, false), passwordLoginDisabled: flag(env.OIDC_DISABLE_PASSWORD_LOGIN, false), }; } diff --git a/tests/oidc.test.ts b/tests/oidc.test.ts index c9de5f8..3550552 100644 --- a/tests/oidc.test.ts +++ b/tests/oidc.test.ts @@ -15,7 +15,7 @@ describe("OIDC config", () => { clientSecret: undefined, name: "SSO", scopes: ["openid", "email", "profile"], - autoRegister: true, + autoRegister: false, passwordLoginDisabled: false, }); });