refactor: better error handling for server side calls (#101)

This commit is contained in:
Bereket Engida
2024-10-05 23:35:01 +03:00
committed by GitHub
parent 7cd1985d0e
commit 0ccf68ceed
20 changed files with 269 additions and 530 deletions

View File

@@ -1,5 +1,6 @@
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { APIError } from "better-auth/api";
import { twoFactor } from "better-auth/plugins";
export const auth = betterAuth({
@@ -11,3 +12,10 @@ export const auth = betterAuth({
),
plugins: [twoFactor()],
});
try {
await auth.api.signOut();
} catch (e) {
if (e instanceof APIError) {
}
}

View File

@@ -42,3 +42,24 @@ await auth.api.getSession({
Unlike the client, the server needs the values to be passed as an object with the key `body` for the body, `headers` for the headers, and `query` for the query.
## Error Handling
When you call an API endpoint in the server, it will throw an error if the request fails. You can catch the error and handle it as you see fit. The error instance is an instance of `APIError`.
```ts title="server.ts"
import { APIError } from "better-auth/api";
try {
await auth.api.signInEmail({
body: {
email: "",
password: ""
}
})
} catch (error) {
if (error instanceof APIError) {
console.log(error.message, error.status)
}
}
```

View File

@@ -254,3 +254,4 @@ export const router = <C extends AuthContext, Option extends BetterAuthOptions>(
export * from "./routes";
export * from "./middlewares";
export * from "./call";
export { APIError } from "better-call";

View File

@@ -3,6 +3,7 @@ import { createJWT, parseJWT, type JWT } from "oslo/jwt";
import { validateJWT } from "oslo/jwt";
import { z } from "zod";
import { createAuthEndpoint } from "../call";
import { APIError } from "better-call";
export const forgetPassword = createAuthEndpoint(
"/forget-password",
@@ -27,17 +28,14 @@ export const forgetPassword = createAuthEndpoint(
ctx.context.logger.error(
"Reset password isn't enabled.Please pass an emailAndPassword.sendResetPasswordToken function to your auth config!",
);
return ctx.json(null, {
status: 400,
statusText: "RESET_PASSWORD_EMAIL_NOT_SENT",
body: {
throw new APIError("BAD_REQUEST", {
message: "Reset password isn't enabled",
},
});
}
const { email } = ctx.body;
const user = await ctx.context.internalAdapter.findUserByEmail(email);
if (!user) {
ctx.context.logger.error("Reset Password: User not found", { email });
//only on the server status is false for the client it's always true
//to avoid leaking information
return ctx.json(
@@ -129,19 +127,9 @@ export const resetPassword = createAuthEndpoint(
async (ctx) => {
const token = ctx.query?.currentURL.split("?token=")[1];
if (!token) {
return ctx.json(
{
error: "Invalid token",
data: null,
},
{
status: 400,
statusText: "INVALID_TOKEN",
body: {
message: "Invalid token",
},
},
);
throw new APIError("BAD_REQUEST", {
message: "Token not found",
});
}
const { newPassword } = ctx.body;
try {
@@ -175,19 +163,9 @@ export const resetPassword = createAuthEndpoint(
newPassword.length >
(ctx.context.options.emailAndPassword?.maxPasswordLength || 32)
) {
return ctx.json(
{
data: null,
error: "password is too short or too long",
},
{
status: 400,
statusText: "INVALID_PASSWORD_LENGTH",
body: {
message: "password is too short or too long",
},
},
);
throw new APIError("BAD_REQUEST", {
message: "Password is too short or too long",
});
}
const hashedPassword = await ctx.context.password.hash(newPassword);
const updatedUser = await ctx.context.internalAdapter.updatePassword(
@@ -195,12 +173,8 @@ export const resetPassword = createAuthEndpoint(
hashedPassword,
);
if (!updatedUser) {
return ctx.json(null, {
status: 400,
statusText: "USER_NOT_FOUND",
body: {
message: "User doesn't have a credential account",
},
throw new APIError("BAD_REQUEST", {
message: "Failed to update password",
});
}
return ctx.json(
@@ -221,20 +195,10 @@ export const resetPassword = createAuthEndpoint(
},
);
} catch (e) {
console.log(e);
return ctx.json(
{
error: "Invalid token",
data: null,
},
{
status: 400,
statusText: "INVALID_TOKEN",
body: {
message: "Invalid token",
},
},
);
ctx.context.logger.error("Failed to reset password", e);
throw new APIError("BAD_REQUEST", {
message: "Failed to reset password",
});
}
},
);

View File

@@ -2,7 +2,7 @@ import { APIError, type Context } from "better-call";
import { createAuthEndpoint, createAuthMiddleware } from "../call";
import { getDate } from "../../utils/date";
import { deleteSessionCookie, setSessionCookie } from "../../utils/cookies";
import type { Session, User } from "../../db/schema";
import type { Session } from "../../db/schema";
import { z } from "zod";
import { getIp } from "../../utils/get-request-ip";
import type {
@@ -205,16 +205,18 @@ export const revokeSession = createAuthEndpoint(
const id = ctx.body.id;
const findSession = await ctx.context.internalAdapter.findSession(id);
if (!findSession) {
return ctx.json(null, { status: 400 });
throw new APIError("BAD_REQUEST", {
message: "Session not found",
});
}
if (findSession.session.userId !== ctx.context.session.user.id) {
return ctx.json(null, { status: 403 });
throw new APIError("UNAUTHORIZED");
}
try {
await ctx.context.internalAdapter.deleteSession(id);
} catch (error) {
ctx.context.logger.error(error);
return ctx.json(null, { status: 500 });
throw new APIError("INTERNAL_SERVER_ERROR");
}
return ctx.json({
status: true,
@@ -238,7 +240,7 @@ export const revokeSessions = createAuthEndpoint(
);
} catch (error) {
ctx.context.logger.error(error);
return ctx.json(null, { status: 500 });
throw new APIError("INTERNAL_SERVER_ERROR");
}
return ctx.json({
status: true,

View File

@@ -3,6 +3,7 @@ import { z } from "zod";
import { createAuthEndpoint } from "../call";
import { createEmailVerificationToken } from "./verify-email";
import { setSessionCookie } from "../../utils/cookies";
import { APIError } from "better-call";
export const signUpEmail = createAuthEndpoint(
"/sign-up/email",
@@ -23,93 +24,37 @@ export const signUpEmail = createAuthEndpoint(
},
async (ctx) => {
if (!ctx.context.options.emailAndPassword?.enabled) {
return ctx.json(
{
user: null,
session: null,
error: {
message: "Email and password is not enabled",
},
},
{
status: 400,
body: {
message: "Email and password is not enabled",
},
},
);
throw new APIError("BAD_REQUEST", {
message: "Email and password sign up is not enabled",
});
}
const { name, email, password, image } = ctx.body;
const isValidEmail = z.string().email().safeParse(email);
if (!isValidEmail.success) {
return ctx.json(
{
user: null,
session: null,
error: {
message: "Invalid email address",
},
},
{
status: 400,
body: {
message: "Invalid email address",
},
},
);
throw new APIError("BAD_REQUEST", {
message: "Invalid email",
});
}
const minPasswordLength = ctx.context.password.config.minPasswordLength;
if (password.length < minPasswordLength) {
ctx.context.logger.error("Password is too short");
return ctx.json(
{
user: null,
session: null,
error: {
throw new APIError("BAD_REQUEST", {
message: "Password is too short",
},
},
{
status: 400,
body: { message: "Password is too short" },
},
);
});
}
const maxPasswordLength = ctx.context.password.config.maxPasswordLength;
if (password.length > maxPasswordLength) {
ctx.context.logger.error("Password is too long");
return ctx.json(
{
user: null,
session: null,
error: {
throw new APIError("BAD_REQUEST", {
message: "Password is too long",
},
},
{
status: 400,
body: { message: "Password is too long" },
},
);
});
}
const dbUser = await ctx.context.internalAdapter.findUserByEmail(email);
if (dbUser?.user) {
return ctx.json(
{
user: null,
session: null,
error: {
throw new APIError("BAD_REQUEST", {
message: "User already exists",
},
},
{
status: 400,
body: {
message: "User already exists",
},
},
);
});
}
const createdUser = await ctx.context.internalAdapter.createUser({
id: generateRandomString(32, alphabet("a-z", "0-9", "A-Z")),
@@ -121,21 +66,9 @@ export const signUpEmail = createAuthEndpoint(
updatedAt: new Date(),
});
if (!createdUser) {
return ctx.json(
{
user: null,
session: null,
error: {
message: "Could not create user",
},
},
{
status: 400,
body: {
message: "Could not create user",
},
},
);
throw new APIError("BAD_REQUEST", {
message: "Couldn't create user",
});
}
/**
* Link the account to the user
@@ -153,21 +86,9 @@ export const signUpEmail = createAuthEndpoint(
ctx.request,
);
if (!session) {
return ctx.json(
{
user: null,
session: null,
error: {
message: "Could not create session",
},
},
{
status: 400,
body: {
message: "Could not create session",
},
},
);
throw new APIError("BAD_REQUEST", {
message: "Couldn't create session",
});
}
await setSessionCookie(ctx, session.id);
if (ctx.context.options.emailAndPassword.sendEmailVerificationOnSignUp) {

View File

@@ -3,6 +3,7 @@ import { createAuthEndpoint } from "../call";
import { alphabet, generateRandomString } from "../../crypto/random";
import { setSessionCookie } from "../../utils/cookies";
import { sessionMiddleware } from "./session";
import { APIError } from "better-call";
export const updateUser = createAuthEndpoint(
"/user/update",
@@ -58,9 +59,8 @@ export const changePassword = createAuthEndpoint(
const minPasswordLength = ctx.context.password.config.minPasswordLength;
if (newPassword.length < minPasswordLength) {
ctx.context.logger.error("Password is too short");
return ctx.json(null, {
status: 400,
body: { message: "Password is too short" },
throw new APIError("BAD_REQUEST", {
message: "Password is too short",
});
}
@@ -68,9 +68,8 @@ export const changePassword = createAuthEndpoint(
if (newPassword.length > maxPasswordLength) {
ctx.context.logger.error("Password is too long");
return ctx.json(null, {
status: 400,
body: { message: "Password is too long" },
throw new APIError("BAD_REQUEST", {
message: "Password too long",
});
}
@@ -81,9 +80,8 @@ export const changePassword = createAuthEndpoint(
(account) => account.providerId === "credential" && account.password,
);
if (!account || !account.password) {
return ctx.json(null, {
status: 400,
body: { message: "User does not have a password" },
throw new APIError("BAD_REQUEST", {
message: "User does not have a password",
});
}
const passwordHash = await ctx.context.password.hash(newPassword);
@@ -92,9 +90,8 @@ export const changePassword = createAuthEndpoint(
currentPassword,
);
if (!verify) {
return ctx.json(null, {
status: 400,
body: { message: "Invalid password" },
throw new APIError("BAD_REQUEST", {
message: "Incorrect password",
});
}
await ctx.context.internalAdapter.updateAccount(account.id, {
@@ -107,9 +104,8 @@ export const changePassword = createAuthEndpoint(
ctx.headers,
);
if (!newSession) {
return ctx.json(null, {
status: 500,
body: { message: "Failed to create session" },
throw new APIError("INTERNAL_SERVER_ERROR", {
message: "Unable to create session",
});
}
// set the new session cookie
@@ -138,9 +134,8 @@ export const setPassword = createAuthEndpoint(
const minPasswordLength = ctx.context.password.config.minPasswordLength;
if (newPassword.length < minPasswordLength) {
ctx.context.logger.error("Password is too short");
return ctx.json(null, {
status: 400,
body: { message: "Password is too short" },
throw new APIError("BAD_REQUEST", {
message: "Password is too short",
});
}
@@ -148,9 +143,8 @@ export const setPassword = createAuthEndpoint(
if (newPassword.length > maxPasswordLength) {
ctx.context.logger.error("Password is too long");
return ctx.json(null, {
status: 400,
body: { message: "Password is too long" },
throw new APIError("BAD_REQUEST", {
message: "Password too long",
});
}
@@ -171,9 +165,8 @@ export const setPassword = createAuthEndpoint(
});
return ctx.json(session.user);
}
return ctx.json(null, {
status: 400,
body: { message: "User already has a password" },
throw new APIError("BAD_REQUEST", {
message: "user already has a password",
});
},
);
@@ -197,9 +190,8 @@ export const deleteUser = createAuthEndpoint(
(account) => account.providerId === "credential" && account.password,
);
if (!account || !account.password) {
return ctx.json(null, {
status: 400,
body: { message: "User does not have a password" },
throw new APIError("BAD_REQUEST", {
message: "User does not have a password",
});
}
const verify = await ctx.context.password.verify(
@@ -207,9 +199,8 @@ export const deleteUser = createAuthEndpoint(
password,
);
if (!verify) {
return ctx.json(null, {
status: 400,
body: { message: "Invalid password" },
throw new APIError("BAD_REQUEST", {
message: "Incorrect password",
});
}
await ctx.context.internalAdapter.deleteUser(session.user.id);

View File

@@ -2,6 +2,7 @@ import { TimeSpan } from "oslo";
import { createJWT, validateJWT, type JWT } from "oslo/jwt";
import { z } from "zod";
import { createAuthEndpoint } from "../call";
import { APIError } from "better-call";
export async function createEmailVerificationToken(
secret: string,
@@ -43,12 +44,8 @@ export const sendVerificationEmail = createAuthEndpoint(
ctx.context.logger.error(
"Verification email isn't enabled. Pass `sendVerificationEmail` in `emailAndPassword` options to enable it.",
);
return ctx.json(null, {
status: 400,
statusText: "VERIFICATION_EMAIL_NOT_SENT",
body: {
throw new APIError("BAD_REQUEST", {
message: "Verification email isn't enabled",
},
});
}
const { email } = ctx.body;

View File

@@ -95,12 +95,8 @@ export const magicLink = (options: MagicLinkOptions) => {
if (callbackURL) {
throw ctx.redirect(`${callbackURL}?error=INVALID_TOKEN`);
}
return ctx.json(null, {
status: 400,
statusText: "INVALID_TOKEN",
body: {
throw new APIError("BAD_REQUEST", {
message: "Invalid token",
},
});
}
const schema = z.object({
@@ -114,12 +110,8 @@ export const magicLink = (options: MagicLinkOptions) => {
if (callbackURL) {
throw ctx.redirect(`${callbackURL}?error=USER_NOT_FOUND`);
}
return ctx.json(null, {
status: 400,
statusText: "USER_NOT_FOUND",
body: {
throw new APIError("BAD_REQUEST", {
message: "User not found",
},
});
}
const session = await ctx.context.internalAdapter.createSession(
@@ -130,12 +122,8 @@ export const magicLink = (options: MagicLinkOptions) => {
if (callbackURL) {
throw ctx.redirect(`${callbackURL}?error=SESSION_NOT_CREATED`);
}
return ctx.json(null, {
status: 400,
statusText: "SESSION NOT CREATED",
body: {
message: "Failed to create session",
},
throw new APIError("INTERNAL_SERVER_ERROR", {
message: "Unable to create session",
});
}
await setSessionCookie(ctx, session.id);

View File

@@ -128,7 +128,7 @@ describe("organization", async (it) => {
headers,
},
});
expect(wrongPerson.error?.status).toBe(400);
expect(wrongPerson.error?.status).toBe(403);
const invitation = await client.organization.acceptInvitation({
invitationId: invite.data.id,

View File

@@ -6,6 +6,7 @@ import { getOrgAdapter } from "../adapter";
import { orgMiddleware, orgSessionMiddleware } from "../call";
import { role } from "../schema";
import { logger } from "../../../utils/logger";
import { APIError } from "better-call";
export const createInvitation = createAuthEndpoint(
"/organization/invite-member",
@@ -24,11 +25,8 @@ export const createInvitation = createAuthEndpoint(
logger.warn(
"Invitation email is not enabled. Pass `sendInvitationEmail` to the plugin options to enable it.",
);
return ctx.json(null, {
status: 400,
body: {
message: "invitation is not enabled",
},
throw new APIError("BAD_REQUEST", {
message: "Invitation email is not enabled",
});
}
@@ -36,11 +34,8 @@ export const createInvitation = createAuthEndpoint(
const orgId =
ctx.body.organizationId || session.session.activeOrganizationId;
if (!orgId) {
return ctx.json(null, {
status: 400,
body: {
message: "Organization id not found!",
},
throw new APIError("BAD_REQUEST", {
message: "Organization not found",
});
}
const adapter = getOrgAdapter(ctx.context.adapter, ctx.context.orgOptions);
@@ -49,31 +44,22 @@ export const createInvitation = createAuthEndpoint(
organizationId: orgId,
});
if (!member) {
return ctx.json(null, {
status: 400,
body: {
message: "User is not a member of this organization!",
},
throw new APIError("BAD_REQUEST", {
message: "Member not found!",
});
}
const role = ctx.context.roles[member.role];
if (!role) {
return ctx.json(null, {
status: 400,
body: {
throw new APIError("BAD_REQUEST", {
message: "Role not found!",
},
});
}
const canInvite = role.authorize({
invitation: ["create"],
});
if (canInvite.error) {
return ctx.json(null, {
body: {
message: "You are not allowed to invite users to this organization",
},
status: 403,
throw new APIError("FORBIDDEN", {
message: "You are not allowed to invite members",
});
}
const alreadyMember = await adapter.findMemberByEmail({
@@ -81,11 +67,8 @@ export const createInvitation = createAuthEndpoint(
organizationId: orgId,
});
if (alreadyMember) {
return ctx.json(null, {
status: 400,
body: {
throw new APIError("BAD_REQUEST", {
message: "User is already a member of this organization",
},
});
}
const alreadyInvited = await adapter.findPendingInvitation({
@@ -93,11 +76,8 @@ export const createInvitation = createAuthEndpoint(
organizationId: orgId,
});
if (alreadyInvited.length && !ctx.body.resend) {
return ctx.json(null, {
status: 400,
body: {
throw new APIError("BAD_REQUEST", {
message: "User is already invited to this organization",
},
});
}
const invitation = await adapter.createInvitation({
@@ -112,11 +92,8 @@ export const createInvitation = createAuthEndpoint(
const organization = await adapter.findOrganizationById(orgId);
if (!organization) {
return ctx.json(null, {
status: 400,
body: {
message: "Organization not found!",
},
throw new APIError("BAD_REQUEST", {
message: "Organization not found",
});
}
@@ -155,19 +132,13 @@ export const acceptInvitation = createAuthEndpoint(
invitation.expiresAt < new Date() ||
invitation.status !== "pending"
) {
return ctx.json(null, {
status: 400,
body: {
throw new APIError("BAD_REQUEST", {
message: "Invitation not found!",
},
});
}
if (invitation.email !== session.user.email) {
return ctx.json(null, {
status: 400,
body: {
throw new APIError("FORBIDDEN", {
message: "You are not the recipient of the invitation",
},
});
}
const acceptedI = await adapter.updateInvitation({
@@ -218,19 +189,13 @@ export const rejectInvitation = createAuthEndpoint(
invitation.expiresAt < new Date() ||
invitation.status !== "pending"
) {
return ctx.json(null, {
status: 400,
body: {
throw new APIError("BAD_REQUEST", {
message: "Invitation not found!",
},
});
}
if (invitation.email !== session.user.email) {
return ctx.json(null, {
status: 400,
body: {
throw new APIError("FORBIDDEN", {
message: "You are not the recipient of the invitation",
},
});
}
const rejectedI = await adapter.updateInvitation({
@@ -258,11 +223,8 @@ export const cancelInvitation = createAuthEndpoint(
const adapter = getOrgAdapter(ctx.context.adapter, ctx.context.orgOptions);
const invitation = await adapter.findInvitationById(ctx.body.invitationId);
if (!invitation) {
return ctx.json(null, {
status: 400,
body: {
throw new APIError("BAD_REQUEST", {
message: "Invitation not found!",
},
});
}
const member = await adapter.findMemberByOrgId({
@@ -270,22 +232,16 @@ export const cancelInvitation = createAuthEndpoint(
organizationId: invitation.organizationId,
});
if (!member) {
return ctx.json(null, {
status: 400,
body: {
message: "User is not a member of this organization",
},
throw new APIError("BAD_REQUEST", {
message: "Member not found!",
});
}
const canCancel = ctx.context.roles[member.role].authorize({
invitation: ["cancel"],
});
if (canCancel.error) {
return ctx.json(null, {
status: 403,
body: {
throw new APIError("FORBIDDEN", {
message: "You are not allowed to cancel this invitation",
},
});
}
const canceledI = await adapter.updateInvitation({
@@ -309,11 +265,8 @@ export const getInvitation = createAuthEndpoint(
async (ctx) => {
const session = await getSessionFromCtx(ctx);
if (!session) {
return ctx.json(null, {
status: 400,
body: {
message: "User not logged in",
},
throw new APIError("UNAUTHORIZED", {
message: "Not authenticated",
});
}
const adapter = getOrgAdapter(ctx.context.adapter, ctx.context.orgOptions);
@@ -323,30 +276,21 @@ export const getInvitation = createAuthEndpoint(
invitation.status !== "pending" ||
invitation.expiresAt < new Date()
) {
return ctx.json(null, {
status: 400,
body: {
throw new APIError("BAD_REQUEST", {
message: "Invitation not found!",
},
});
}
if (invitation.email !== session.user.email) {
return ctx.json(null, {
status: 400,
body: {
throw new APIError("FORBIDDEN", {
message: "You are not the recipient of the invitation",
},
});
}
const organization = await adapter.findOrganizationById(
invitation.organizationId,
);
if (!organization) {
return ctx.json(null, {
status: 400,
body: {
message: "Organization not found!",
},
throw new APIError("BAD_REQUEST", {
message: "Organization not found",
});
}
const member = await adapter.findMemberByOrgId({
@@ -354,11 +298,8 @@ export const getInvitation = createAuthEndpoint(
organizationId: invitation.organizationId,
});
if (!member) {
return ctx.json(null, {
status: 400,
body: {
message: "Inviter is no longer a member of this organization",
},
throw new APIError("BAD_REQUEST", {
message: "Inviter is no longer a member of the organization",
});
}
return ctx.json({

View File

@@ -3,6 +3,7 @@ import { createAuthEndpoint } from "../../../api/call";
import { getOrgAdapter } from "../adapter";
import { orgMiddleware, orgSessionMiddleware } from "../call";
import type { Member } from "../schema";
import { APIError } from "better-call";
export const removeMember = createAuthEndpoint(
"/organization/remove-member",
@@ -35,20 +36,14 @@ export const removeMember = createAuthEndpoint(
organizationId: orgId,
});
if (!member) {
return ctx.json(null, {
status: 400,
body: {
throw new APIError("BAD_REQUEST", {
message: "Member not found!",
},
});
}
const role = ctx.context.roles[member.role];
if (!role) {
return ctx.json(null, {
status: 400,
body: {
throw new APIError("BAD_REQUEST", {
message: "Role not found!",
},
});
}
const isLeaving =
@@ -58,11 +53,8 @@ export const removeMember = createAuthEndpoint(
isLeaving &&
member.role === (ctx.context.orgOptions?.creatorRole || "owner");
if (isOwnerLeaving) {
return ctx.json(null, {
status: 400,
body: {
throw new APIError("BAD_REQUEST", {
message: "You cannot leave the organization as the owner",
},
});
}
@@ -72,11 +64,8 @@ export const removeMember = createAuthEndpoint(
member: ["delete"],
}).success;
if (!canDeleteMember) {
return ctx.json(null, {
body: {
throw new APIError("UNAUTHORIZED", {
message: "You are not allowed to delete this member",
},
status: 403,
});
}
let existing: Member | null = null;
@@ -89,11 +78,8 @@ export const removeMember = createAuthEndpoint(
existing = await adapter.findMemberById(ctx.body.memberIdOrEmail);
}
if (existing?.organizationId !== orgId) {
return ctx.json(null, {
status: 400,
body: {
throw new APIError("BAD_REQUEST", {
message: "Member not found!",
},
});
}
await adapter.deleteMember(existing.id);

View File

@@ -3,6 +3,7 @@ import { createAuthEndpoint } from "../../../api/call";
import { generateId } from "../../../utils/id";
import { getOrgAdapter } from "../adapter";
import { orgMiddleware, orgSessionMiddleware } from "../call";
import { APIError } from "better-call";
export const createOrganization = createAuthEndpoint(
"/organization/create",
@@ -33,11 +34,8 @@ export const createOrganization = createAuthEndpoint(
: options.allowUserToCreateOrganization;
if (!canCreateOrg) {
return ctx.json(null, {
status: 403,
body: {
message: "You are not allowed to create organizations",
},
throw new APIError("FORBIDDEN", {
message: "You are not allowed to create an organization",
});
}
const adapter = getOrgAdapter(ctx.context.adapter, options);
@@ -51,11 +49,8 @@ export const createOrganization = createAuthEndpoint(
: false;
if (hasReachedOrgLimit) {
return ctx.json(null, {
status: 403,
body: {
message: "You have reached the maximum number of organizations",
},
throw new APIError("FORBIDDEN", {
message: "You have reached the organization limit",
});
}
@@ -63,11 +58,8 @@ export const createOrganization = createAuthEndpoint(
ctx.body.slug,
);
if (existingOrganization) {
return ctx.json(null, {
status: 400,
body: {
throw new APIError("BAD_REQUEST", {
message: "Organization with this slug already exists",
},
});
}
const organization = await adapter.createOrganization({
@@ -104,8 +96,8 @@ export const updateOrganization = createAuthEndpoint(
async (ctx) => {
const session = await ctx.context.getSession(ctx);
if (!session) {
return ctx.json(null, {
status: 401,
throw new APIError("UNAUTHORIZED", {
message: "User not found",
});
}
const orgId = ctx.body.orgId || session.session.activeOrganizationId;
@@ -207,11 +199,8 @@ export const deleteOrganization = createAuthEndpoint(
organization: ["delete"],
});
if (canDeleteOrg.error) {
return ctx.json(null, {
body: {
throw new APIError("FORBIDDEN", {
message: "You are not allowed to delete this organization",
},
status: 403,
});
}
if (orgId === session.session.activeOrganizationId) {
@@ -239,11 +228,8 @@ export const getFullOrganization = createAuthEndpoint(
const session = ctx.context.session;
const orgId = ctx.query.orgId || session.session.activeOrganizationId;
if (!orgId) {
return ctx.json(null, {
status: 400,
body: {
message: "Organization id not found!",
},
throw new APIError("BAD_REQUEST", {
message: "Organization not found",
});
}
const adapter = getOrgAdapter(ctx.context.adapter, ctx.context.orgOptions);
@@ -252,11 +238,8 @@ export const getFullOrganization = createAuthEndpoint(
ctx.context.db || undefined,
);
if (!organization) {
return ctx.json(null, {
status: 404,
body: {
message: "Organization not found!",
},
throw new APIError("BAD_REQUEST", {
message: "Organization not found",
});
}
return ctx.json(organization);
@@ -297,11 +280,8 @@ export const setActiveOrganization = createAuthEndpoint(
});
if (!isMember) {
await adapter.setActiveOrganization(session.session.id, null);
return ctx.json(null, {
status: 400,
body: {
throw new APIError("FORBIDDEN", {
message: "You are not a member of this organization",
},
});
}
await adapter.setActiveOrganization(session.session.id, orgId);

View File

@@ -264,12 +264,8 @@ export const passkey = (options?: PasskeyOptions) => {
ctx.context.secret,
);
if (!challengeId) {
return ctx.json(null, {
status: 400,
statusText: "No challenge found",
body: {
message: "No challenge found",
},
throw new APIError("BAD_REQUEST", {
message: "Challenge not found",
});
}
@@ -335,11 +331,8 @@ export const passkey = (options?: PasskeyOptions) => {
});
} catch (e) {
console.log(e);
return ctx.json(null, {
status: 400,
body: {
message: "Registration failed",
},
throw new APIError("INTERNAL_SERVER_ERROR", {
message: "Failed to verify registration",
});
}
},
@@ -355,8 +348,8 @@ export const passkey = (options?: PasskeyOptions) => {
async (ctx) => {
const origin = options?.origin || ctx.headers?.get("origin") || "";
if (!origin) {
return ctx.json(null, {
status: 400,
throw new APIError("BAD_REQUEST", {
message: "origin missing",
});
}
const resp = ctx.body.response;
@@ -365,8 +358,8 @@ export const passkey = (options?: PasskeyOptions) => {
ctx.context.secret,
);
if (!challengeId) {
return ctx.json(null, {
status: 400,
throw new APIError("BAD_REQUEST", {
message: "Challenge not found",
});
}
@@ -375,8 +368,8 @@ export const passkey = (options?: PasskeyOptions) => {
challengeId,
);
if (!data) {
return ctx.json(null, {
status: 400,
throw new APIError("BAD_REQUEST", {
message: "Challenge not found",
});
}
const { expectedChallenge, callbackURL } = JSON.parse(
@@ -392,11 +385,8 @@ export const passkey = (options?: PasskeyOptions) => {
],
});
if (!passkey) {
return ctx.json(null, {
status: 401,
body: {
throw new APIError("UNAUTHORIZED", {
message: "Passkey not found",
},
});
}
try {
@@ -418,11 +408,8 @@ export const passkey = (options?: PasskeyOptions) => {
});
const { verified } = verification;
if (!verified)
return ctx.json(null, {
status: 401,
body: {
message: "verification failed",
},
throw new APIError("UNAUTHORIZED", {
message: "Authentication failed",
});
await ctx.context.adapter.update<Passkey>({
@@ -442,11 +429,8 @@ export const passkey = (options?: PasskeyOptions) => {
ctx.request,
);
if (!s) {
return ctx.json(null, {
status: 500,
body: {
message: "Failed to create session",
},
throw new APIError("INTERNAL_SERVER_ERROR", {
message: "Unable to create session",
});
}
await setSessionCookie(ctx, s.id);
@@ -467,11 +451,8 @@ export const passkey = (options?: PasskeyOptions) => {
);
} catch (e) {
ctx.context.logger.error(e);
return ctx.json(null, {
status: 400,
body: {
message: "Authentication failed",
},
throw new APIError("BAD_REQUEST", {
message: "Failed to verify authentication",
});
}
},

View File

@@ -220,6 +220,7 @@ export const phoneNumber = (options?: {
},
);
}
try {
const res = await signUpEmail({
...ctx,
//@ts-expect-error
@@ -228,21 +229,7 @@ export const phoneNumber = (options?: {
},
_flag: undefined,
});
if (res.error) {
return ctx.json(
{
user: null,
session: null,
},
{
status: 400,
body: {
message: res.error.message,
status: 400,
},
},
);
}
if (options?.otp?.sendOTPonSignUp) {
if (!options.otp.sendOTP) {
logger.warn("sendOTP not implemented");
@@ -285,6 +272,14 @@ export const phoneNumber = (options?: {
user: updated,
session: res.session,
});
} catch (e) {
if (e instanceof APIError) {
throw e;
}
throw new APIError("INTERNAL_SERVER_ERROR", {
message: "Failed to create user",
});
}
},
),
sendVerificationCode: createAuthEndpoint(
@@ -338,31 +333,18 @@ export const phoneNumber = (options?: {
if (!otp || otp.expiresAt < new Date()) {
if (otp && otp.expiresAt < new Date()) {
await ctx.context.internalAdapter.deleteVerificationValue(otp.id);
throw new APIError("BAD_REQUEST", {
message: "OTP expired",
});
}
return ctx.json(
{
status: false,
},
{
body: {
message: "Invalid code",
},
status: 400,
},
);
throw new APIError("BAD_REQUEST", {
message: "OTP not found",
});
}
if (otp.value !== ctx.body.code) {
return ctx.json(
{
status: false,
},
{
body: {
message: "Invalid code",
},
status: 400,
},
);
throw new APIError("BAD_REQUEST", {
message: "Invalid OTP",
});
}
await ctx.context.internalAdapter.deleteVerificationValue(otp.id);
const user = await ctx.context.adapter.findOne<User>({

View File

@@ -5,6 +5,7 @@ import { sessionMiddleware } from "../../../api";
import { symmetricDecrypt, symmetricEncrypt } from "../../../crypto";
import { verifyTwoFactorMiddleware } from "../verify-middleware";
import type { TwoFactorProvider, UserWithTwoFactor } from "../types";
import { APIError } from "better-call";
export interface BackupCodeOptions {
/**
@@ -98,12 +99,9 @@ export const backupCode2fa = (options?: BackupCodeOptions) => {
ctx.context.secret,
);
if (!validate) {
return ctx.json(
{ status: false },
{
status: 401,
},
);
throw new APIError("BAD_REQUEST", {
message: "Invalid backup code",
});
}
return ctx.json({ status: true });
},

View File

@@ -11,6 +11,7 @@ import type { TwoFactorOptions, UserWithTwoFactor } from "./types";
import type { Session } from "../../db/schema";
import { TWO_FACTOR_COOKIE_NAME, TRUST_DEVICE_COOKIE_NAME } from "./constant";
import { validatePassword } from "../../utils/password";
import { APIError } from "better-call";
export const twoFactor = (options?: TwoFactorOptions) => {
const totp = totp2fa({
@@ -42,15 +43,9 @@ export const twoFactor = (options?: TwoFactorOptions) => {
userId: user.id,
});
if (!isPasswordValid) {
return ctx.json(
{ status: false },
{
status: 400,
body: {
throw new APIError("BAD_REQUEST", {
message: "Invalid password",
},
},
);
});
}
const secret = generateRandomString(16, alphabet("a-z", "0-9", "-"));
const encryptedSecret = symmetricEncrypt({
@@ -95,15 +90,9 @@ export const twoFactor = (options?: TwoFactorOptions) => {
userId: user.id,
});
if (!isPasswordValid) {
return ctx.json(
{ status: false },
{
status: 400,
body: {
throw new APIError("BAD_REQUEST", {
message: "Invalid password",
},
},
);
});
}
await ctx.context.adapter.update({
model: "user",

View File

@@ -112,7 +112,7 @@ export const totp2fa = (options: TOTPOptions) => {
}
const totp = new TOTPController(opts);
const secret = Buffer.from(
await symmetricDecrypt({
symmetricDecrypt({
key: ctx.context.secret,
data: ctx.context.session.user.twoFactorSecret,
}),

View File

@@ -111,15 +111,9 @@ export const verifyTwoFactorMiddleware = createAuthMiddleware(
return ctx.json({ status: true });
},
invalid: async () => {
return ctx.json(
{ status: false },
{
status: 401,
body: {
message: "Invalid code",
},
},
);
throw new APIError("UNAUTHORIZED", {
message: "invalid two factor authentication",
});
},
session: {
id: session.id,

View File

@@ -1,5 +0,0 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.