@zpg6-test-pkgs/better-auth
Version:
The most comprehensive authentication library for TypeScript.
420 lines (415 loc) • 15 kB
JavaScript
import * as z from 'zod/v4';
import { c as createAuthMiddleware, a as createAuthEndpoint, B as BASE_ERROR_CODES } from '../../shared/better-auth.BfeJWAMn.mjs';
import { APIError } from 'better-call';
import { s as setSessionCookie } from '../../shared/better-auth.DF-MUmVw.mjs';
import { T as sendVerificationEmailFn } from '../../shared/better-auth.D7aTFyWE.mjs';
import { m as mergeSchema } from '../../shared/better-auth.n2KFGwjY.mjs';
import '../../shared/better-auth.CMQ3rA-I.mjs';
import '../../shared/better-auth.BjBlybv-.mjs';
import '../../shared/better-auth.CW6D9eSx.mjs';
import '../../shared/better-auth.BZZKN1g7.mjs';
import '@better-auth/utils/hmac';
import '@better-auth/utils/base64';
import '@better-auth/utils/binary';
import '../../shared/better-auth.DdzSJf-n.mjs';
import '../../shared/better-auth.CuS_eDdK.mjs';
import '@better-auth/utils/hash';
import '../../crypto/index.mjs';
import '@noble/ciphers/chacha';
import '@noble/ciphers/utils';
import '@noble/ciphers/webcrypto';
import 'jose';
import '@noble/hashes/scrypt';
import '@better-auth/utils';
import '@better-auth/utils/hex';
import '@noble/hashes/utils';
import '../../shared/better-auth.B4Qoxdgc.mjs';
import '@better-auth/utils/random';
import '@better-fetch/fetch';
import 'jose/errors';
import '../../shared/better-auth.BUPPRXfK.mjs';
import 'defu';
const getSchema = (normalizer) => {
return {
user: {
fields: {
username: {
type: "string",
required: false,
sortable: true,
unique: true,
returned: true,
transform: {
input(value) {
return value == null ? value : normalizer.username(value);
}
}
},
displayUsername: {
type: "string",
required: false,
transform: {
input(value) {
return value == null ? value : normalizer.displayUsername(value);
}
}
}
}
}
};
};
const USERNAME_ERROR_CODES = {
INVALID_USERNAME_OR_PASSWORD: "Invalid username or password",
EMAIL_NOT_VERIFIED: "Email not verified",
UNEXPECTED_ERROR: "Unexpected error",
USERNAME_IS_ALREADY_TAKEN: "Username is already taken. Please try another.",
USERNAME_TOO_SHORT: "Username is too short",
USERNAME_TOO_LONG: "Username is too long",
INVALID_USERNAME: "Username is invalid",
INVALID_DISPLAY_USERNAME: "Display username is invalid"
};
function defaultUsernameValidator(username2) {
return /^[a-zA-Z0-9_.]+$/.test(username2);
}
const username = (options) => {
const normalizer = (username2) => {
if (options?.usernameNormalization === false) {
return username2;
}
if (options?.usernameNormalization) {
return options.usernameNormalization(username2);
}
return username2.toLowerCase();
};
const displayUsernameNormalizer = (displayUsername) => {
return options?.displayUsernameNormalization ? options.displayUsernameNormalization(displayUsername) : displayUsername;
};
return {
id: "username",
init(ctx) {
return {
options: {
databaseHooks: {
user: {
create: {
async before(user, context) {
const username2 = "username" in user ? user.username : null;
const displayUsername = "displayUsername" in user ? user.displayUsername : null;
return {
data: {
...user,
...username2 ? { username: normalizer(username2) } : {},
...displayUsername ? {
displayUsername: displayUsernameNormalizer(displayUsername)
} : {}
}
};
}
},
update: {
async before(user, context) {
const username2 = "username" in user ? user.username : null;
const displayUsername = "displayUsername" in user ? user.displayUsername : null;
return {
data: {
...user,
...username2 ? { username: normalizer(username2) } : {},
...displayUsername ? {
displayUsername: displayUsernameNormalizer(displayUsername)
} : {}
}
};
}
}
}
}
}
};
},
endpoints: {
signInUsername: createAuthEndpoint(
"/sign-in/username",
{
method: "POST",
body: z.object({
username: z.string().meta({ description: "The username of the user" }),
password: z.string().meta({ description: "The password of the user" }),
rememberMe: z.boolean().meta({
description: "Remember the user session"
}).optional(),
callbackURL: z.string().meta({
description: "The URL to redirect to after email verification"
}).optional()
}),
metadata: {
openapi: {
summary: "Sign in with username",
description: "Sign in with username",
responses: {
200: {
description: "Success",
content: {
"application/json": {
schema: {
type: "object",
properties: {
token: {
type: "string",
description: "Session token for the authenticated session"
},
user: {
$ref: "#/components/schemas/User"
}
},
required: ["token", "user"]
}
}
}
}
}
}
}
},
async (ctx) => {
if (!ctx.body.username || !ctx.body.password) {
ctx.context.logger.error("Username or password not found");
throw new APIError("UNAUTHORIZED", {
message: USERNAME_ERROR_CODES.INVALID_USERNAME_OR_PASSWORD
});
}
const username2 = options?.validationOrder?.username === "pre-normalization" ? normalizer(ctx.body.username) : ctx.body.username;
const minUsernameLength = options?.minUsernameLength || 3;
const maxUsernameLength = options?.maxUsernameLength || 30;
if (username2.length < minUsernameLength) {
ctx.context.logger.error("Username too short", {
username: username2
});
throw new APIError("UNPROCESSABLE_ENTITY", {
message: USERNAME_ERROR_CODES.USERNAME_TOO_SHORT
});
}
if (username2.length > maxUsernameLength) {
ctx.context.logger.error("Username too long", {
username: username2
});
throw new APIError("UNPROCESSABLE_ENTITY", {
message: USERNAME_ERROR_CODES.USERNAME_TOO_LONG
});
}
const validator = options?.usernameValidator || defaultUsernameValidator;
if (!validator(username2)) {
throw new APIError("UNPROCESSABLE_ENTITY", {
message: USERNAME_ERROR_CODES.INVALID_USERNAME
});
}
const user = await ctx.context.adapter.findOne({
model: "user",
where: [
{
field: "username",
value: username2
}
]
});
if (!user) {
await ctx.context.password.hash(ctx.body.password);
ctx.context.logger.error("User not found", {
username: username2
});
throw new APIError("UNAUTHORIZED", {
message: USERNAME_ERROR_CODES.INVALID_USERNAME_OR_PASSWORD
});
}
if (!user.emailVerified && ctx.context.options.emailAndPassword?.requireEmailVerification) {
await sendVerificationEmailFn(ctx, user);
throw new APIError("FORBIDDEN", {
message: USERNAME_ERROR_CODES.EMAIL_NOT_VERIFIED
});
}
const account = await ctx.context.adapter.findOne({
model: "account",
where: [
{
field: "userId",
value: user.id
},
{
field: "providerId",
value: "credential"
}
]
});
if (!account) {
throw new APIError("UNAUTHORIZED", {
message: USERNAME_ERROR_CODES.INVALID_USERNAME_OR_PASSWORD
});
}
const currentPassword = account?.password;
if (!currentPassword) {
ctx.context.logger.error("Password not found", {
username: username2
});
throw new APIError("UNAUTHORIZED", {
message: USERNAME_ERROR_CODES.INVALID_USERNAME_OR_PASSWORD
});
}
const validPassword = await ctx.context.password.verify({
hash: currentPassword,
password: ctx.body.password
});
if (!validPassword) {
ctx.context.logger.error("Invalid password");
throw new APIError("UNAUTHORIZED", {
message: USERNAME_ERROR_CODES.INVALID_USERNAME_OR_PASSWORD
});
}
const session = await ctx.context.internalAdapter.createSession(
user.id,
ctx,
ctx.body.rememberMe === false
);
if (!session) {
return ctx.json(null, {
status: 500,
body: {
message: BASE_ERROR_CODES.FAILED_TO_CREATE_SESSION
}
});
}
await setSessionCookie(
ctx,
{ session, user },
ctx.body.rememberMe === false
);
return ctx.json({
token: session.token,
user: {
id: user.id,
email: user.email,
emailVerified: user.emailVerified,
username: user.username,
name: user.name,
image: user.image,
createdAt: user.createdAt,
updatedAt: user.updatedAt
}
});
}
),
isUsernameAvailable: createAuthEndpoint(
"/is-username-available",
{
method: "POST",
body: z.object({
username: z.string().meta({
description: "The username to check"
})
})
},
async (ctx) => {
const username2 = ctx.body.username;
if (!username2) {
throw new APIError("UNPROCESSABLE_ENTITY", {
message: USERNAME_ERROR_CODES.INVALID_USERNAME
});
}
const user = await ctx.context.adapter.findOne({
model: "user",
where: [
{
field: "username",
value: username2.toLowerCase()
}
]
});
if (user) {
return ctx.json({
available: false
});
}
return ctx.json({
available: true
});
}
)
},
schema: mergeSchema(
getSchema({
username: normalizer,
displayUsername: displayUsernameNormalizer
}),
options?.schema
),
hooks: {
before: [
{
matcher(context) {
return context.path === "/sign-up/email" || context.path === "/update-user";
},
handler: createAuthMiddleware(async (ctx) => {
const username2 = typeof ctx.body.username === "string" && options?.validationOrder?.username === "post-normalization" ? normalizer(ctx.body.username) : ctx.body.username;
if (username2 !== void 0 && typeof username2 === "string") {
const minUsernameLength = options?.minUsernameLength || 3;
const maxUsernameLength = options?.maxUsernameLength || 30;
if (username2.length < minUsernameLength) {
throw new APIError("BAD_REQUEST", {
message: USERNAME_ERROR_CODES.USERNAME_TOO_SHORT
});
}
if (username2.length > maxUsernameLength) {
throw new APIError("BAD_REQUEST", {
message: USERNAME_ERROR_CODES.USERNAME_TOO_LONG
});
}
const validator = options?.usernameValidator || defaultUsernameValidator;
const valid = await validator(username2);
if (!valid) {
throw new APIError("BAD_REQUEST", {
message: USERNAME_ERROR_CODES.INVALID_USERNAME
});
}
const user = await ctx.context.adapter.findOne({
model: "user",
where: [
{
field: "username",
value: username2
}
]
});
const blockChangeSignUp = ctx.path === "/sign-up/email" && user;
const blockChangeUpdateUser = ctx.path === "/update-user" && user && ctx.context.session && user.id !== ctx.context.session.session.userId;
if (blockChangeSignUp || blockChangeUpdateUser) {
throw new APIError("BAD_REQUEST", {
message: USERNAME_ERROR_CODES.USERNAME_IS_ALREADY_TAKEN
});
}
}
const displayUsername = typeof ctx.body.displayUsername === "string" && options?.validationOrder?.displayUsername === "post-normalization" ? displayUsernameNormalizer(ctx.body.displayUsername) : ctx.body.displayUsername;
if (displayUsername !== void 0 && typeof displayUsername === "string") {
if (options?.displayUsernameValidator) {
const valid = await options.displayUsernameValidator(displayUsername);
if (!valid) {
throw new APIError("BAD_REQUEST", {
message: USERNAME_ERROR_CODES.INVALID_DISPLAY_USERNAME
});
}
}
}
})
},
{
matcher(context) {
return context.path === "/sign-up/email" || context.path === "/update-user";
},
handler: createAuthMiddleware(async (ctx) => {
ctx.body.displayUsername ||= ctx.body.username;
ctx.body.username ||= ctx.body.displayUsername;
})
}
]
},
$ERROR_CODES: USERNAME_ERROR_CODES
};
};
export { USERNAME_ERROR_CODES, username };