better-auth
Version:
The most comprehensive authentication framework for TypeScript.
224 lines (222 loc) • 8 kB
JavaScript
import { parseUserInput, parseUserOutput } from "../../db/schema.mjs";
import "../../db/index.mjs";
import { formCsrfMiddleware } from "../middlewares/origin-check.mjs";
import { setSessionCookie } from "../../cookies/index.mjs";
import { createEmailVerificationToken } from "./email-verification.mjs";
import { runWithTransaction } from "@better-auth/core/context";
import { isDevelopment } from "@better-auth/core/env";
import { BASE_ERROR_CODES } from "@better-auth/core/error";
import { APIError } from "better-call";
import * as z from "zod";
import { createAuthEndpoint } from "@better-auth/core/api";
//#region src/api/routes/sign-up.ts
const signUpEmailBodySchema = z.object({
name: z.string(),
email: z.email(),
password: z.string().nonempty(),
image: z.string().optional(),
callbackURL: z.string().optional(),
rememberMe: z.boolean().optional()
}).and(z.record(z.string(), z.any()));
const signUpEmail = () => createAuthEndpoint("/sign-up/email", {
method: "POST",
operationId: "signUpWithEmailAndPassword",
use: [formCsrfMiddleware],
body: signUpEmailBodySchema,
metadata: {
allowedMediaTypes: ["application/x-www-form-urlencoded", "application/json"],
$Infer: {
body: {},
returned: {}
},
openapi: {
operationId: "signUpWithEmailAndPassword",
description: "Sign up a user using email and password",
requestBody: { content: { "application/json": { schema: {
type: "object",
properties: {
name: {
type: "string",
description: "The name of the user"
},
email: {
type: "string",
description: "The email of the user"
},
password: {
type: "string",
description: "The password of the user"
},
image: {
type: "string",
description: "The profile image URL of the user"
},
callbackURL: {
type: "string",
description: "The URL to use for email verification callback"
},
rememberMe: {
type: "boolean",
description: "If this is false, the session will not be remembered. Default is `true`."
}
},
required: [
"name",
"email",
"password"
]
} } } },
responses: {
"200": {
description: "Successfully created user",
content: { "application/json": { schema: {
type: "object",
properties: {
token: {
type: "string",
nullable: true,
description: "Authentication token for the session"
},
user: {
type: "object",
properties: {
id: {
type: "string",
description: "The unique identifier of the user"
},
email: {
type: "string",
format: "email",
description: "The email address of the user"
},
name: {
type: "string",
description: "The name of the user"
},
image: {
type: "string",
format: "uri",
nullable: true,
description: "The profile image URL of the user"
},
emailVerified: {
type: "boolean",
description: "Whether the email has been verified"
},
createdAt: {
type: "string",
format: "date-time",
description: "When the user was created"
},
updatedAt: {
type: "string",
format: "date-time",
description: "When the user was last updated"
}
},
required: [
"id",
"email",
"name",
"emailVerified",
"createdAt",
"updatedAt"
]
}
},
required: ["user"]
} } }
},
"422": {
description: "Unprocessable Entity. User already exists or failed to create user.",
content: { "application/json": { schema: {
type: "object",
properties: { message: { type: "string" } }
} } }
}
}
}
}
}, async (ctx) => {
return runWithTransaction(ctx.context.adapter, async () => {
if (!ctx.context.options.emailAndPassword?.enabled || ctx.context.options.emailAndPassword?.disableSignUp) throw new APIError("BAD_REQUEST", { message: "Email and password sign up is not enabled" });
const body = ctx.body;
const { name, email, password, image, callbackURL: _callbackURL, rememberMe, ...rest } = body;
if (!z.email().safeParse(email).success) throw new APIError("BAD_REQUEST", { message: BASE_ERROR_CODES.INVALID_EMAIL });
if (!password || typeof password !== "string") throw new APIError("BAD_REQUEST", { message: BASE_ERROR_CODES.INVALID_PASSWORD });
const minPasswordLength = ctx.context.password.config.minPasswordLength;
if (password.length < minPasswordLength) {
ctx.context.logger.error("Password is too short");
throw new APIError("BAD_REQUEST", { message: BASE_ERROR_CODES.PASSWORD_TOO_SHORT });
}
const maxPasswordLength = ctx.context.password.config.maxPasswordLength;
if (password.length > maxPasswordLength) {
ctx.context.logger.error("Password is too long");
throw new APIError("BAD_REQUEST", { message: BASE_ERROR_CODES.PASSWORD_TOO_LONG });
}
if ((await ctx.context.internalAdapter.findUserByEmail(email))?.user) {
ctx.context.logger.info(`Sign-up attempt for existing email: ${email}`);
throw new APIError("UNPROCESSABLE_ENTITY", { message: BASE_ERROR_CODES.USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL });
}
/**
* Hash the password
*
* This is done prior to creating the user
* to ensure that any plugin that
* may break the hashing should break
* before the user is created.
*/
const hash = await ctx.context.password.hash(password);
let createdUser;
try {
const data = parseUserInput(ctx.context.options, rest, "create");
createdUser = await ctx.context.internalAdapter.createUser({
email: email.toLowerCase(),
name,
image,
...data,
emailVerified: false
});
if (!createdUser) throw new APIError("BAD_REQUEST", { message: BASE_ERROR_CODES.FAILED_TO_CREATE_USER });
} catch (e) {
if (isDevelopment()) ctx.context.logger.error("Failed to create user", e);
if (e instanceof APIError) throw e;
ctx.context.logger?.error("Failed to create user", e);
throw new APIError("UNPROCESSABLE_ENTITY", { message: BASE_ERROR_CODES.FAILED_TO_CREATE_USER });
}
if (!createdUser) throw new APIError("UNPROCESSABLE_ENTITY", { message: BASE_ERROR_CODES.FAILED_TO_CREATE_USER });
await ctx.context.internalAdapter.linkAccount({
userId: createdUser.id,
providerId: "credential",
accountId: createdUser.id,
password: hash
});
if (ctx.context.options.emailVerification?.sendOnSignUp ?? ctx.context.options.emailAndPassword.requireEmailVerification) {
const token = await createEmailVerificationToken(ctx.context.secret, createdUser.email, void 0, ctx.context.options.emailVerification?.expiresIn);
const callbackURL = body.callbackURL ? encodeURIComponent(body.callbackURL) : encodeURIComponent("/");
const url = `${ctx.context.baseURL}/verify-email?token=${token}&callbackURL=${callbackURL}`;
if (ctx.context.options.emailVerification?.sendVerificationEmail) await ctx.context.runInBackgroundOrAwait(ctx.context.options.emailVerification.sendVerificationEmail({
user: createdUser,
url,
token
}, ctx.request));
}
if (ctx.context.options.emailAndPassword.autoSignIn === false || ctx.context.options.emailAndPassword.requireEmailVerification) return ctx.json({
token: null,
user: parseUserOutput(ctx.context.options, createdUser)
});
const session = await ctx.context.internalAdapter.createSession(createdUser.id, rememberMe === false);
if (!session) throw new APIError("BAD_REQUEST", { message: BASE_ERROR_CODES.FAILED_TO_CREATE_SESSION });
await setSessionCookie(ctx, {
session,
user: createdUser
}, rememberMe === false);
return ctx.json({
token: session.token,
user: parseUserOutput(ctx.context.options, createdUser)
});
});
});
//#endregion
export { signUpEmail };
//# sourceMappingURL=sign-up.mjs.map