Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

</details>
Expand Down
23 changes: 19 additions & 4 deletions src/app/actions/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,27 @@ export async function createUserAction(input: z.input<typeof userSchema>) {
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 };
Expand Down
11 changes: 11 additions & 0 deletions src/lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/oidc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
};
}
Expand Down
2 changes: 1 addition & 1 deletion tests/oidc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe("OIDC config", () => {
clientSecret: undefined,
name: "SSO",
scopes: ["openid", "email", "profile"],
autoRegister: true,
autoRegister: false,
passwordLoginDisabled: false,
});
});
Expand Down
Loading