Show a disabled-registration state on the register page when DISABLE_SIGNUPS is true (default), document docker-deploy.sh with git-SHA image tags, and align onboarding progress circles and labels on a shared grid. Co-authored-by: Cursor <cursoragent@cursor.com>
124 lines
3.5 KiB
TypeScript
124 lines
3.5 KiB
TypeScript
import { expo } from "@better-auth/expo";
|
|
import { betterAuth } from "better-auth";
|
|
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
|
import { nextCookies } from "better-auth/next-js";
|
|
import { genericOAuth } from "better-auth/plugins";
|
|
import { env } from "~/env";
|
|
import { isDemoUser, promoteFirstRealUserIfNeeded } from "~/lib/first-admin";
|
|
import { db } from "~/server/db";
|
|
import * as schema from "~/server/db/schema";
|
|
|
|
const authentikEnabled = Boolean(
|
|
process.env.AUTHENTIK_ISSUER &&
|
|
process.env.AUTHENTIK_CLIENT_ID &&
|
|
process.env.AUTHENTIK_CLIENT_SECRET,
|
|
);
|
|
const signupsDisabled = env.DISABLE_SIGNUPS;
|
|
|
|
// Derive the authentik origin from the issuer URL so the OAuth callback is
|
|
// automatically trusted without needing a separate AUTHENTIK_ORIGIN env var.
|
|
const authentikOrigin =
|
|
authentikEnabled && process.env.AUTHENTIK_ISSUER
|
|
? new URL(process.env.AUTHENTIK_ISSUER).origin
|
|
: null;
|
|
|
|
const staticTrustedOrigins = [
|
|
...(process.env.BETTER_AUTH_URL ? [process.env.BETTER_AUTH_URL] : []),
|
|
...(process.env.NEXT_PUBLIC_APP_URL ? [process.env.NEXT_PUBLIC_APP_URL] : []),
|
|
"beenvoice://",
|
|
"exp://",
|
|
...(authentikOrigin ? [authentikOrigin] : []),
|
|
...(process.env.AUTHENTIK_ORIGIN ? [process.env.AUTHENTIK_ORIGIN] : []),
|
|
];
|
|
|
|
export const auth = betterAuth({
|
|
baseURL: process.env.BETTER_AUTH_URL,
|
|
secret: process.env.AUTH_SECRET,
|
|
advanced: {
|
|
trustedProxyHeaders: true,
|
|
},
|
|
experimental: {
|
|
joins: true,
|
|
},
|
|
database: drizzleAdapter(db, {
|
|
provider: "pg",
|
|
schema: {
|
|
user: schema.users,
|
|
session: schema.sessions,
|
|
account: schema.accounts,
|
|
verification: schema.verificationTokens,
|
|
},
|
|
}),
|
|
databaseHooks: {
|
|
user: {
|
|
create: {
|
|
after: async (user) => {
|
|
if (isDemoUser(user)) {
|
|
return;
|
|
}
|
|
await promoteFirstRealUserIfNeeded(user.id);
|
|
},
|
|
},
|
|
},
|
|
},
|
|
trustedOrigins: async (request) => {
|
|
const origins = [...staticTrustedOrigins];
|
|
|
|
if (!request) return origins;
|
|
|
|
const origin = request.headers.get("origin");
|
|
if (origin) origins.push(origin);
|
|
|
|
const forwardedHost = request.headers.get("x-forwarded-host");
|
|
const forwardedProto = request.headers.get("x-forwarded-proto") ?? "https";
|
|
if (forwardedHost) {
|
|
for (const host of forwardedHost.split(",")) {
|
|
const trimmed = host.trim();
|
|
if (trimmed) origins.push(`${forwardedProto}://${trimmed}`);
|
|
}
|
|
}
|
|
|
|
return origins;
|
|
},
|
|
...(authentikEnabled && {
|
|
accountLinking: {
|
|
enabled: true,
|
|
trustedProviders: ["authentik"],
|
|
},
|
|
}),
|
|
emailAndPassword: {
|
|
enabled: true,
|
|
disableSignUp: signupsDisabled,
|
|
password: {
|
|
hash: async (password) => {
|
|
const bcrypt = await import("bcryptjs");
|
|
return bcrypt.hash(password, 12);
|
|
},
|
|
verify: async ({ hash, password }) => {
|
|
const bcrypt = await import("bcryptjs");
|
|
return bcrypt.compare(password, hash);
|
|
},
|
|
},
|
|
},
|
|
plugins: [
|
|
expo(),
|
|
nextCookies(),
|
|
...(authentikEnabled
|
|
? [
|
|
genericOAuth({
|
|
config: [
|
|
{
|
|
providerId: "authentik",
|
|
clientId: process.env.AUTHENTIK_CLIENT_ID!,
|
|
clientSecret: process.env.AUTHENTIK_CLIENT_SECRET!,
|
|
discoveryUrl: `${process.env.AUTHENTIK_ISSUER}/.well-known/openid-configuration`,
|
|
scopes: ["openid", "email", "profile"],
|
|
pkce: true,
|
|
},
|
|
],
|
|
}),
|
|
]
|
|
: []),
|
|
],
|
|
});
|