mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-07 04:19:22 +00:00
refactor: better error handling for server side calls (#101)
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from "better-auth";
|
||||||
import { prismaAdapter } from "better-auth/adapters/prisma";
|
import { prismaAdapter } from "better-auth/adapters/prisma";
|
||||||
|
import { APIError } from "better-auth/api";
|
||||||
import { twoFactor } from "better-auth/plugins";
|
import { twoFactor } from "better-auth/plugins";
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
@@ -11,3 +12,10 @@ export const auth = betterAuth({
|
|||||||
),
|
),
|
||||||
plugins: [twoFactor()],
|
plugins: [twoFactor()],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await auth.api.signOut();
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof APIError) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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.
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
@@ -254,3 +254,4 @@ export const router = <C extends AuthContext, Option extends BetterAuthOptions>(
|
|||||||
export * from "./routes";
|
export * from "./routes";
|
||||||
export * from "./middlewares";
|
export * from "./middlewares";
|
||||||
export * from "./call";
|
export * from "./call";
|
||||||
|
export { APIError } from "better-call";
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { createJWT, parseJWT, type JWT } from "oslo/jwt";
|
|||||||
import { validateJWT } from "oslo/jwt";
|
import { validateJWT } from "oslo/jwt";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { createAuthEndpoint } from "../call";
|
import { createAuthEndpoint } from "../call";
|
||||||
|
import { APIError } from "better-call";
|
||||||
|
|
||||||
export const forgetPassword = createAuthEndpoint(
|
export const forgetPassword = createAuthEndpoint(
|
||||||
"/forget-password",
|
"/forget-password",
|
||||||
@@ -27,17 +28,14 @@ export const forgetPassword = createAuthEndpoint(
|
|||||||
ctx.context.logger.error(
|
ctx.context.logger.error(
|
||||||
"Reset password isn't enabled.Please pass an emailAndPassword.sendResetPasswordToken function to your auth config!",
|
"Reset password isn't enabled.Please pass an emailAndPassword.sendResetPasswordToken function to your auth config!",
|
||||||
);
|
);
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Reset password isn't enabled",
|
||||||
statusText: "RESET_PASSWORD_EMAIL_NOT_SENT",
|
|
||||||
body: {
|
|
||||||
message: "Reset password isn't enabled",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const { email } = ctx.body;
|
const { email } = ctx.body;
|
||||||
const user = await ctx.context.internalAdapter.findUserByEmail(email);
|
const user = await ctx.context.internalAdapter.findUserByEmail(email);
|
||||||
if (!user) {
|
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
|
//only on the server status is false for the client it's always true
|
||||||
//to avoid leaking information
|
//to avoid leaking information
|
||||||
return ctx.json(
|
return ctx.json(
|
||||||
@@ -129,19 +127,9 @@ export const resetPassword = createAuthEndpoint(
|
|||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const token = ctx.query?.currentURL.split("?token=")[1];
|
const token = ctx.query?.currentURL.split("?token=")[1];
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return ctx.json(
|
throw new APIError("BAD_REQUEST", {
|
||||||
{
|
message: "Token not found",
|
||||||
error: "Invalid token",
|
});
|
||||||
data: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 400,
|
|
||||||
statusText: "INVALID_TOKEN",
|
|
||||||
body: {
|
|
||||||
message: "Invalid token",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const { newPassword } = ctx.body;
|
const { newPassword } = ctx.body;
|
||||||
try {
|
try {
|
||||||
@@ -175,19 +163,9 @@ export const resetPassword = createAuthEndpoint(
|
|||||||
newPassword.length >
|
newPassword.length >
|
||||||
(ctx.context.options.emailAndPassword?.maxPasswordLength || 32)
|
(ctx.context.options.emailAndPassword?.maxPasswordLength || 32)
|
||||||
) {
|
) {
|
||||||
return ctx.json(
|
throw new APIError("BAD_REQUEST", {
|
||||||
{
|
message: "Password is too short or too long",
|
||||||
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",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const hashedPassword = await ctx.context.password.hash(newPassword);
|
const hashedPassword = await ctx.context.password.hash(newPassword);
|
||||||
const updatedUser = await ctx.context.internalAdapter.updatePassword(
|
const updatedUser = await ctx.context.internalAdapter.updatePassword(
|
||||||
@@ -195,12 +173,8 @@ export const resetPassword = createAuthEndpoint(
|
|||||||
hashedPassword,
|
hashedPassword,
|
||||||
);
|
);
|
||||||
if (!updatedUser) {
|
if (!updatedUser) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Failed to update password",
|
||||||
statusText: "USER_NOT_FOUND",
|
|
||||||
body: {
|
|
||||||
message: "User doesn't have a credential account",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return ctx.json(
|
return ctx.json(
|
||||||
@@ -221,20 +195,10 @@ export const resetPassword = createAuthEndpoint(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
ctx.context.logger.error("Failed to reset password", e);
|
||||||
return ctx.json(
|
throw new APIError("BAD_REQUEST", {
|
||||||
{
|
message: "Failed to reset password",
|
||||||
error: "Invalid token",
|
});
|
||||||
data: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 400,
|
|
||||||
statusText: "INVALID_TOKEN",
|
|
||||||
body: {
|
|
||||||
message: "Invalid token",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { APIError, type Context } from "better-call";
|
|||||||
import { createAuthEndpoint, createAuthMiddleware } from "../call";
|
import { createAuthEndpoint, createAuthMiddleware } from "../call";
|
||||||
import { getDate } from "../../utils/date";
|
import { getDate } from "../../utils/date";
|
||||||
import { deleteSessionCookie, setSessionCookie } from "../../utils/cookies";
|
import { deleteSessionCookie, setSessionCookie } from "../../utils/cookies";
|
||||||
import type { Session, User } from "../../db/schema";
|
import type { Session } from "../../db/schema";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { getIp } from "../../utils/get-request-ip";
|
import { getIp } from "../../utils/get-request-ip";
|
||||||
import type {
|
import type {
|
||||||
@@ -205,16 +205,18 @@ export const revokeSession = createAuthEndpoint(
|
|||||||
const id = ctx.body.id;
|
const id = ctx.body.id;
|
||||||
const findSession = await ctx.context.internalAdapter.findSession(id);
|
const findSession = await ctx.context.internalAdapter.findSession(id);
|
||||||
if (!findSession) {
|
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) {
|
if (findSession.session.userId !== ctx.context.session.user.id) {
|
||||||
return ctx.json(null, { status: 403 });
|
throw new APIError("UNAUTHORIZED");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await ctx.context.internalAdapter.deleteSession(id);
|
await ctx.context.internalAdapter.deleteSession(id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ctx.context.logger.error(error);
|
ctx.context.logger.error(error);
|
||||||
return ctx.json(null, { status: 500 });
|
throw new APIError("INTERNAL_SERVER_ERROR");
|
||||||
}
|
}
|
||||||
return ctx.json({
|
return ctx.json({
|
||||||
status: true,
|
status: true,
|
||||||
@@ -238,7 +240,7 @@ export const revokeSessions = createAuthEndpoint(
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ctx.context.logger.error(error);
|
ctx.context.logger.error(error);
|
||||||
return ctx.json(null, { status: 500 });
|
throw new APIError("INTERNAL_SERVER_ERROR");
|
||||||
}
|
}
|
||||||
return ctx.json({
|
return ctx.json({
|
||||||
status: true,
|
status: true,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|||||||
import { createAuthEndpoint } from "../call";
|
import { createAuthEndpoint } from "../call";
|
||||||
import { createEmailVerificationToken } from "./verify-email";
|
import { createEmailVerificationToken } from "./verify-email";
|
||||||
import { setSessionCookie } from "../../utils/cookies";
|
import { setSessionCookie } from "../../utils/cookies";
|
||||||
|
import { APIError } from "better-call";
|
||||||
|
|
||||||
export const signUpEmail = createAuthEndpoint(
|
export const signUpEmail = createAuthEndpoint(
|
||||||
"/sign-up/email",
|
"/sign-up/email",
|
||||||
@@ -23,93 +24,37 @@ export const signUpEmail = createAuthEndpoint(
|
|||||||
},
|
},
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
if (!ctx.context.options.emailAndPassword?.enabled) {
|
if (!ctx.context.options.emailAndPassword?.enabled) {
|
||||||
return ctx.json(
|
throw new APIError("BAD_REQUEST", {
|
||||||
{
|
message: "Email and password sign up is not enabled",
|
||||||
user: null,
|
});
|
||||||
session: null,
|
|
||||||
error: {
|
|
||||||
message: "Email and password is not enabled",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 400,
|
|
||||||
body: {
|
|
||||||
message: "Email and password is not enabled",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const { name, email, password, image } = ctx.body;
|
const { name, email, password, image } = ctx.body;
|
||||||
const isValidEmail = z.string().email().safeParse(email);
|
const isValidEmail = z.string().email().safeParse(email);
|
||||||
if (!isValidEmail.success) {
|
if (!isValidEmail.success) {
|
||||||
return ctx.json(
|
throw new APIError("BAD_REQUEST", {
|
||||||
{
|
message: "Invalid email",
|
||||||
user: null,
|
});
|
||||||
session: null,
|
|
||||||
error: {
|
|
||||||
message: "Invalid email address",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 400,
|
|
||||||
body: {
|
|
||||||
message: "Invalid email address",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const minPasswordLength = ctx.context.password.config.minPasswordLength;
|
const minPasswordLength = ctx.context.password.config.minPasswordLength;
|
||||||
if (password.length < minPasswordLength) {
|
if (password.length < minPasswordLength) {
|
||||||
ctx.context.logger.error("Password is too short");
|
ctx.context.logger.error("Password is too short");
|
||||||
return ctx.json(
|
throw new APIError("BAD_REQUEST", {
|
||||||
{
|
message: "Password is too short",
|
||||||
user: null,
|
});
|
||||||
session: null,
|
|
||||||
error: {
|
|
||||||
message: "Password is too short",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 400,
|
|
||||||
body: { message: "Password is too short" },
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const maxPasswordLength = ctx.context.password.config.maxPasswordLength;
|
const maxPasswordLength = ctx.context.password.config.maxPasswordLength;
|
||||||
if (password.length > maxPasswordLength) {
|
if (password.length > maxPasswordLength) {
|
||||||
ctx.context.logger.error("Password is too long");
|
ctx.context.logger.error("Password is too long");
|
||||||
return ctx.json(
|
throw new APIError("BAD_REQUEST", {
|
||||||
{
|
message: "Password is too long",
|
||||||
user: null,
|
});
|
||||||
session: null,
|
|
||||||
error: {
|
|
||||||
message: "Password is too long",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 400,
|
|
||||||
body: { message: "Password is too long" },
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const dbUser = await ctx.context.internalAdapter.findUserByEmail(email);
|
const dbUser = await ctx.context.internalAdapter.findUserByEmail(email);
|
||||||
if (dbUser?.user) {
|
if (dbUser?.user) {
|
||||||
return ctx.json(
|
throw new APIError("BAD_REQUEST", {
|
||||||
{
|
message: "User already exists",
|
||||||
user: null,
|
});
|
||||||
session: null,
|
|
||||||
error: {
|
|
||||||
message: "User already exists",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 400,
|
|
||||||
body: {
|
|
||||||
message: "User already exists",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const createdUser = await ctx.context.internalAdapter.createUser({
|
const createdUser = await ctx.context.internalAdapter.createUser({
|
||||||
id: generateRandomString(32, alphabet("a-z", "0-9", "A-Z")),
|
id: generateRandomString(32, alphabet("a-z", "0-9", "A-Z")),
|
||||||
@@ -121,21 +66,9 @@ export const signUpEmail = createAuthEndpoint(
|
|||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
});
|
});
|
||||||
if (!createdUser) {
|
if (!createdUser) {
|
||||||
return ctx.json(
|
throw new APIError("BAD_REQUEST", {
|
||||||
{
|
message: "Couldn't create user",
|
||||||
user: null,
|
});
|
||||||
session: null,
|
|
||||||
error: {
|
|
||||||
message: "Could not create user",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 400,
|
|
||||||
body: {
|
|
||||||
message: "Could not create user",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Link the account to the user
|
* Link the account to the user
|
||||||
@@ -153,21 +86,9 @@ export const signUpEmail = createAuthEndpoint(
|
|||||||
ctx.request,
|
ctx.request,
|
||||||
);
|
);
|
||||||
if (!session) {
|
if (!session) {
|
||||||
return ctx.json(
|
throw new APIError("BAD_REQUEST", {
|
||||||
{
|
message: "Couldn't create session",
|
||||||
user: null,
|
});
|
||||||
session: null,
|
|
||||||
error: {
|
|
||||||
message: "Could not create session",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 400,
|
|
||||||
body: {
|
|
||||||
message: "Could not create session",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
await setSessionCookie(ctx, session.id);
|
await setSessionCookie(ctx, session.id);
|
||||||
if (ctx.context.options.emailAndPassword.sendEmailVerificationOnSignUp) {
|
if (ctx.context.options.emailAndPassword.sendEmailVerificationOnSignUp) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { createAuthEndpoint } from "../call";
|
|||||||
import { alphabet, generateRandomString } from "../../crypto/random";
|
import { alphabet, generateRandomString } from "../../crypto/random";
|
||||||
import { setSessionCookie } from "../../utils/cookies";
|
import { setSessionCookie } from "../../utils/cookies";
|
||||||
import { sessionMiddleware } from "./session";
|
import { sessionMiddleware } from "./session";
|
||||||
|
import { APIError } from "better-call";
|
||||||
|
|
||||||
export const updateUser = createAuthEndpoint(
|
export const updateUser = createAuthEndpoint(
|
||||||
"/user/update",
|
"/user/update",
|
||||||
@@ -58,9 +59,8 @@ export const changePassword = createAuthEndpoint(
|
|||||||
const minPasswordLength = ctx.context.password.config.minPasswordLength;
|
const minPasswordLength = ctx.context.password.config.minPasswordLength;
|
||||||
if (newPassword.length < minPasswordLength) {
|
if (newPassword.length < minPasswordLength) {
|
||||||
ctx.context.logger.error("Password is too short");
|
ctx.context.logger.error("Password is too short");
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Password is too short",
|
||||||
body: { message: "Password is too short" },
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,9 +68,8 @@ export const changePassword = createAuthEndpoint(
|
|||||||
|
|
||||||
if (newPassword.length > maxPasswordLength) {
|
if (newPassword.length > maxPasswordLength) {
|
||||||
ctx.context.logger.error("Password is too long");
|
ctx.context.logger.error("Password is too long");
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Password too long",
|
||||||
body: { message: "Password is too long" },
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,9 +80,8 @@ export const changePassword = createAuthEndpoint(
|
|||||||
(account) => account.providerId === "credential" && account.password,
|
(account) => account.providerId === "credential" && account.password,
|
||||||
);
|
);
|
||||||
if (!account || !account.password) {
|
if (!account || !account.password) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "User does not have a password",
|
||||||
body: { message: "User does not have a password" },
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const passwordHash = await ctx.context.password.hash(newPassword);
|
const passwordHash = await ctx.context.password.hash(newPassword);
|
||||||
@@ -92,9 +90,8 @@ export const changePassword = createAuthEndpoint(
|
|||||||
currentPassword,
|
currentPassword,
|
||||||
);
|
);
|
||||||
if (!verify) {
|
if (!verify) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Incorrect password",
|
||||||
body: { message: "Invalid password" },
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await ctx.context.internalAdapter.updateAccount(account.id, {
|
await ctx.context.internalAdapter.updateAccount(account.id, {
|
||||||
@@ -107,9 +104,8 @@ export const changePassword = createAuthEndpoint(
|
|||||||
ctx.headers,
|
ctx.headers,
|
||||||
);
|
);
|
||||||
if (!newSession) {
|
if (!newSession) {
|
||||||
return ctx.json(null, {
|
throw new APIError("INTERNAL_SERVER_ERROR", {
|
||||||
status: 500,
|
message: "Unable to create session",
|
||||||
body: { message: "Failed to create session" },
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// set the new session cookie
|
// set the new session cookie
|
||||||
@@ -138,9 +134,8 @@ export const setPassword = createAuthEndpoint(
|
|||||||
const minPasswordLength = ctx.context.password.config.minPasswordLength;
|
const minPasswordLength = ctx.context.password.config.minPasswordLength;
|
||||||
if (newPassword.length < minPasswordLength) {
|
if (newPassword.length < minPasswordLength) {
|
||||||
ctx.context.logger.error("Password is too short");
|
ctx.context.logger.error("Password is too short");
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Password is too short",
|
||||||
body: { message: "Password is too short" },
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,9 +143,8 @@ export const setPassword = createAuthEndpoint(
|
|||||||
|
|
||||||
if (newPassword.length > maxPasswordLength) {
|
if (newPassword.length > maxPasswordLength) {
|
||||||
ctx.context.logger.error("Password is too long");
|
ctx.context.logger.error("Password is too long");
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Password too long",
|
||||||
body: { message: "Password is too long" },
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,9 +165,8 @@ export const setPassword = createAuthEndpoint(
|
|||||||
});
|
});
|
||||||
return ctx.json(session.user);
|
return ctx.json(session.user);
|
||||||
}
|
}
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "user already has a password",
|
||||||
body: { message: "User already has a password" },
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -197,9 +190,8 @@ export const deleteUser = createAuthEndpoint(
|
|||||||
(account) => account.providerId === "credential" && account.password,
|
(account) => account.providerId === "credential" && account.password,
|
||||||
);
|
);
|
||||||
if (!account || !account.password) {
|
if (!account || !account.password) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "User does not have a password",
|
||||||
body: { message: "User does not have a password" },
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const verify = await ctx.context.password.verify(
|
const verify = await ctx.context.password.verify(
|
||||||
@@ -207,9 +199,8 @@ export const deleteUser = createAuthEndpoint(
|
|||||||
password,
|
password,
|
||||||
);
|
);
|
||||||
if (!verify) {
|
if (!verify) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Incorrect password",
|
||||||
body: { message: "Invalid password" },
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await ctx.context.internalAdapter.deleteUser(session.user.id);
|
await ctx.context.internalAdapter.deleteUser(session.user.id);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { TimeSpan } from "oslo";
|
|||||||
import { createJWT, validateJWT, type JWT } from "oslo/jwt";
|
import { createJWT, validateJWT, type JWT } from "oslo/jwt";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { createAuthEndpoint } from "../call";
|
import { createAuthEndpoint } from "../call";
|
||||||
|
import { APIError } from "better-call";
|
||||||
|
|
||||||
export async function createEmailVerificationToken(
|
export async function createEmailVerificationToken(
|
||||||
secret: string,
|
secret: string,
|
||||||
@@ -43,12 +44,8 @@ export const sendVerificationEmail = createAuthEndpoint(
|
|||||||
ctx.context.logger.error(
|
ctx.context.logger.error(
|
||||||
"Verification email isn't enabled. Pass `sendVerificationEmail` in `emailAndPassword` options to enable it.",
|
"Verification email isn't enabled. Pass `sendVerificationEmail` in `emailAndPassword` options to enable it.",
|
||||||
);
|
);
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Verification email isn't enabled",
|
||||||
statusText: "VERIFICATION_EMAIL_NOT_SENT",
|
|
||||||
body: {
|
|
||||||
message: "Verification email isn't enabled",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const { email } = ctx.body;
|
const { email } = ctx.body;
|
||||||
|
|||||||
@@ -95,12 +95,8 @@ export const magicLink = (options: MagicLinkOptions) => {
|
|||||||
if (callbackURL) {
|
if (callbackURL) {
|
||||||
throw ctx.redirect(`${callbackURL}?error=INVALID_TOKEN`);
|
throw ctx.redirect(`${callbackURL}?error=INVALID_TOKEN`);
|
||||||
}
|
}
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Invalid token",
|
||||||
statusText: "INVALID_TOKEN",
|
|
||||||
body: {
|
|
||||||
message: "Invalid token",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
@@ -114,12 +110,8 @@ export const magicLink = (options: MagicLinkOptions) => {
|
|||||||
if (callbackURL) {
|
if (callbackURL) {
|
||||||
throw ctx.redirect(`${callbackURL}?error=USER_NOT_FOUND`);
|
throw ctx.redirect(`${callbackURL}?error=USER_NOT_FOUND`);
|
||||||
}
|
}
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "User not found",
|
||||||
statusText: "USER_NOT_FOUND",
|
|
||||||
body: {
|
|
||||||
message: "User not found",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const session = await ctx.context.internalAdapter.createSession(
|
const session = await ctx.context.internalAdapter.createSession(
|
||||||
@@ -130,12 +122,8 @@ export const magicLink = (options: MagicLinkOptions) => {
|
|||||||
if (callbackURL) {
|
if (callbackURL) {
|
||||||
throw ctx.redirect(`${callbackURL}?error=SESSION_NOT_CREATED`);
|
throw ctx.redirect(`${callbackURL}?error=SESSION_NOT_CREATED`);
|
||||||
}
|
}
|
||||||
return ctx.json(null, {
|
throw new APIError("INTERNAL_SERVER_ERROR", {
|
||||||
status: 400,
|
message: "Unable to create session",
|
||||||
statusText: "SESSION NOT CREATED",
|
|
||||||
body: {
|
|
||||||
message: "Failed to create session",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await setSessionCookie(ctx, session.id);
|
await setSessionCookie(ctx, session.id);
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ describe("organization", async (it) => {
|
|||||||
headers,
|
headers,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(wrongPerson.error?.status).toBe(400);
|
expect(wrongPerson.error?.status).toBe(403);
|
||||||
|
|
||||||
const invitation = await client.organization.acceptInvitation({
|
const invitation = await client.organization.acceptInvitation({
|
||||||
invitationId: invite.data.id,
|
invitationId: invite.data.id,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { getOrgAdapter } from "../adapter";
|
|||||||
import { orgMiddleware, orgSessionMiddleware } from "../call";
|
import { orgMiddleware, orgSessionMiddleware } from "../call";
|
||||||
import { role } from "../schema";
|
import { role } from "../schema";
|
||||||
import { logger } from "../../../utils/logger";
|
import { logger } from "../../../utils/logger";
|
||||||
|
import { APIError } from "better-call";
|
||||||
|
|
||||||
export const createInvitation = createAuthEndpoint(
|
export const createInvitation = createAuthEndpoint(
|
||||||
"/organization/invite-member",
|
"/organization/invite-member",
|
||||||
@@ -24,11 +25,8 @@ export const createInvitation = createAuthEndpoint(
|
|||||||
logger.warn(
|
logger.warn(
|
||||||
"Invitation email is not enabled. Pass `sendInvitationEmail` to the plugin options to enable it.",
|
"Invitation email is not enabled. Pass `sendInvitationEmail` to the plugin options to enable it.",
|
||||||
);
|
);
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Invitation email is not enabled",
|
||||||
body: {
|
|
||||||
message: "invitation is not enabled",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,11 +34,8 @@ export const createInvitation = createAuthEndpoint(
|
|||||||
const orgId =
|
const orgId =
|
||||||
ctx.body.organizationId || session.session.activeOrganizationId;
|
ctx.body.organizationId || session.session.activeOrganizationId;
|
||||||
if (!orgId) {
|
if (!orgId) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Organization not found",
|
||||||
body: {
|
|
||||||
message: "Organization id not found!",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const adapter = getOrgAdapter(ctx.context.adapter, ctx.context.orgOptions);
|
const adapter = getOrgAdapter(ctx.context.adapter, ctx.context.orgOptions);
|
||||||
@@ -49,31 +44,22 @@ export const createInvitation = createAuthEndpoint(
|
|||||||
organizationId: orgId,
|
organizationId: orgId,
|
||||||
});
|
});
|
||||||
if (!member) {
|
if (!member) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Member not found!",
|
||||||
body: {
|
|
||||||
message: "User is not a member of this organization!",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const role = ctx.context.roles[member.role];
|
const role = ctx.context.roles[member.role];
|
||||||
if (!role) {
|
if (!role) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Role not found!",
|
||||||
body: {
|
|
||||||
message: "Role not found!",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const canInvite = role.authorize({
|
const canInvite = role.authorize({
|
||||||
invitation: ["create"],
|
invitation: ["create"],
|
||||||
});
|
});
|
||||||
if (canInvite.error) {
|
if (canInvite.error) {
|
||||||
return ctx.json(null, {
|
throw new APIError("FORBIDDEN", {
|
||||||
body: {
|
message: "You are not allowed to invite members",
|
||||||
message: "You are not allowed to invite users to this organization",
|
|
||||||
},
|
|
||||||
status: 403,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const alreadyMember = await adapter.findMemberByEmail({
|
const alreadyMember = await adapter.findMemberByEmail({
|
||||||
@@ -81,11 +67,8 @@ export const createInvitation = createAuthEndpoint(
|
|||||||
organizationId: orgId,
|
organizationId: orgId,
|
||||||
});
|
});
|
||||||
if (alreadyMember) {
|
if (alreadyMember) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "User is already a member of this organization",
|
||||||
body: {
|
|
||||||
message: "User is already a member of this organization",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const alreadyInvited = await adapter.findPendingInvitation({
|
const alreadyInvited = await adapter.findPendingInvitation({
|
||||||
@@ -93,11 +76,8 @@ export const createInvitation = createAuthEndpoint(
|
|||||||
organizationId: orgId,
|
organizationId: orgId,
|
||||||
});
|
});
|
||||||
if (alreadyInvited.length && !ctx.body.resend) {
|
if (alreadyInvited.length && !ctx.body.resend) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "User is already invited to this organization",
|
||||||
body: {
|
|
||||||
message: "User is already invited to this organization",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const invitation = await adapter.createInvitation({
|
const invitation = await adapter.createInvitation({
|
||||||
@@ -112,11 +92,8 @@ export const createInvitation = createAuthEndpoint(
|
|||||||
const organization = await adapter.findOrganizationById(orgId);
|
const organization = await adapter.findOrganizationById(orgId);
|
||||||
|
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Organization not found",
|
||||||
body: {
|
|
||||||
message: "Organization not found!",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,19 +132,13 @@ export const acceptInvitation = createAuthEndpoint(
|
|||||||
invitation.expiresAt < new Date() ||
|
invitation.expiresAt < new Date() ||
|
||||||
invitation.status !== "pending"
|
invitation.status !== "pending"
|
||||||
) {
|
) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Invitation not found!",
|
||||||
body: {
|
|
||||||
message: "Invitation not found!",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (invitation.email !== session.user.email) {
|
if (invitation.email !== session.user.email) {
|
||||||
return ctx.json(null, {
|
throw new APIError("FORBIDDEN", {
|
||||||
status: 400,
|
message: "You are not the recipient of the invitation",
|
||||||
body: {
|
|
||||||
message: "You are not the recipient of the invitation",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const acceptedI = await adapter.updateInvitation({
|
const acceptedI = await adapter.updateInvitation({
|
||||||
@@ -218,19 +189,13 @@ export const rejectInvitation = createAuthEndpoint(
|
|||||||
invitation.expiresAt < new Date() ||
|
invitation.expiresAt < new Date() ||
|
||||||
invitation.status !== "pending"
|
invitation.status !== "pending"
|
||||||
) {
|
) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Invitation not found!",
|
||||||
body: {
|
|
||||||
message: "Invitation not found!",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (invitation.email !== session.user.email) {
|
if (invitation.email !== session.user.email) {
|
||||||
return ctx.json(null, {
|
throw new APIError("FORBIDDEN", {
|
||||||
status: 400,
|
message: "You are not the recipient of the invitation",
|
||||||
body: {
|
|
||||||
message: "You are not the recipient of the invitation",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const rejectedI = await adapter.updateInvitation({
|
const rejectedI = await adapter.updateInvitation({
|
||||||
@@ -258,11 +223,8 @@ export const cancelInvitation = createAuthEndpoint(
|
|||||||
const adapter = getOrgAdapter(ctx.context.adapter, ctx.context.orgOptions);
|
const adapter = getOrgAdapter(ctx.context.adapter, ctx.context.orgOptions);
|
||||||
const invitation = await adapter.findInvitationById(ctx.body.invitationId);
|
const invitation = await adapter.findInvitationById(ctx.body.invitationId);
|
||||||
if (!invitation) {
|
if (!invitation) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Invitation not found!",
|
||||||
body: {
|
|
||||||
message: "Invitation not found!",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const member = await adapter.findMemberByOrgId({
|
const member = await adapter.findMemberByOrgId({
|
||||||
@@ -270,22 +232,16 @@ export const cancelInvitation = createAuthEndpoint(
|
|||||||
organizationId: invitation.organizationId,
|
organizationId: invitation.organizationId,
|
||||||
});
|
});
|
||||||
if (!member) {
|
if (!member) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Member not found!",
|
||||||
body: {
|
|
||||||
message: "User is not a member of this organization",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const canCancel = ctx.context.roles[member.role].authorize({
|
const canCancel = ctx.context.roles[member.role].authorize({
|
||||||
invitation: ["cancel"],
|
invitation: ["cancel"],
|
||||||
});
|
});
|
||||||
if (canCancel.error) {
|
if (canCancel.error) {
|
||||||
return ctx.json(null, {
|
throw new APIError("FORBIDDEN", {
|
||||||
status: 403,
|
message: "You are not allowed to cancel this invitation",
|
||||||
body: {
|
|
||||||
message: "You are not allowed to cancel this invitation",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const canceledI = await adapter.updateInvitation({
|
const canceledI = await adapter.updateInvitation({
|
||||||
@@ -309,11 +265,8 @@ export const getInvitation = createAuthEndpoint(
|
|||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const session = await getSessionFromCtx(ctx);
|
const session = await getSessionFromCtx(ctx);
|
||||||
if (!session) {
|
if (!session) {
|
||||||
return ctx.json(null, {
|
throw new APIError("UNAUTHORIZED", {
|
||||||
status: 400,
|
message: "Not authenticated",
|
||||||
body: {
|
|
||||||
message: "User not logged in",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const adapter = getOrgAdapter(ctx.context.adapter, ctx.context.orgOptions);
|
const adapter = getOrgAdapter(ctx.context.adapter, ctx.context.orgOptions);
|
||||||
@@ -323,30 +276,21 @@ export const getInvitation = createAuthEndpoint(
|
|||||||
invitation.status !== "pending" ||
|
invitation.status !== "pending" ||
|
||||||
invitation.expiresAt < new Date()
|
invitation.expiresAt < new Date()
|
||||||
) {
|
) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Invitation not found!",
|
||||||
body: {
|
|
||||||
message: "Invitation not found!",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (invitation.email !== session.user.email) {
|
if (invitation.email !== session.user.email) {
|
||||||
return ctx.json(null, {
|
throw new APIError("FORBIDDEN", {
|
||||||
status: 400,
|
message: "You are not the recipient of the invitation",
|
||||||
body: {
|
|
||||||
message: "You are not the recipient of the invitation",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const organization = await adapter.findOrganizationById(
|
const organization = await adapter.findOrganizationById(
|
||||||
invitation.organizationId,
|
invitation.organizationId,
|
||||||
);
|
);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Organization not found",
|
||||||
body: {
|
|
||||||
message: "Organization not found!",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const member = await adapter.findMemberByOrgId({
|
const member = await adapter.findMemberByOrgId({
|
||||||
@@ -354,11 +298,8 @@ export const getInvitation = createAuthEndpoint(
|
|||||||
organizationId: invitation.organizationId,
|
organizationId: invitation.organizationId,
|
||||||
});
|
});
|
||||||
if (!member) {
|
if (!member) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Inviter is no longer a member of the organization",
|
||||||
body: {
|
|
||||||
message: "Inviter is no longer a member of this organization",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return ctx.json({
|
return ctx.json({
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { createAuthEndpoint } from "../../../api/call";
|
|||||||
import { getOrgAdapter } from "../adapter";
|
import { getOrgAdapter } from "../adapter";
|
||||||
import { orgMiddleware, orgSessionMiddleware } from "../call";
|
import { orgMiddleware, orgSessionMiddleware } from "../call";
|
||||||
import type { Member } from "../schema";
|
import type { Member } from "../schema";
|
||||||
|
import { APIError } from "better-call";
|
||||||
|
|
||||||
export const removeMember = createAuthEndpoint(
|
export const removeMember = createAuthEndpoint(
|
||||||
"/organization/remove-member",
|
"/organization/remove-member",
|
||||||
@@ -35,20 +36,14 @@ export const removeMember = createAuthEndpoint(
|
|||||||
organizationId: orgId,
|
organizationId: orgId,
|
||||||
});
|
});
|
||||||
if (!member) {
|
if (!member) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Member not found!",
|
||||||
body: {
|
|
||||||
message: "Member not found!",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const role = ctx.context.roles[member.role];
|
const role = ctx.context.roles[member.role];
|
||||||
if (!role) {
|
if (!role) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Role not found!",
|
||||||
body: {
|
|
||||||
message: "Role not found!",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const isLeaving =
|
const isLeaving =
|
||||||
@@ -58,11 +53,8 @@ export const removeMember = createAuthEndpoint(
|
|||||||
isLeaving &&
|
isLeaving &&
|
||||||
member.role === (ctx.context.orgOptions?.creatorRole || "owner");
|
member.role === (ctx.context.orgOptions?.creatorRole || "owner");
|
||||||
if (isOwnerLeaving) {
|
if (isOwnerLeaving) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "You cannot leave the organization as the owner",
|
||||||
body: {
|
|
||||||
message: "You cannot leave the organization as the owner",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,11 +64,8 @@ export const removeMember = createAuthEndpoint(
|
|||||||
member: ["delete"],
|
member: ["delete"],
|
||||||
}).success;
|
}).success;
|
||||||
if (!canDeleteMember) {
|
if (!canDeleteMember) {
|
||||||
return ctx.json(null, {
|
throw new APIError("UNAUTHORIZED", {
|
||||||
body: {
|
message: "You are not allowed to delete this member",
|
||||||
message: "You are not allowed to delete this member",
|
|
||||||
},
|
|
||||||
status: 403,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let existing: Member | null = null;
|
let existing: Member | null = null;
|
||||||
@@ -89,11 +78,8 @@ export const removeMember = createAuthEndpoint(
|
|||||||
existing = await adapter.findMemberById(ctx.body.memberIdOrEmail);
|
existing = await adapter.findMemberById(ctx.body.memberIdOrEmail);
|
||||||
}
|
}
|
||||||
if (existing?.organizationId !== orgId) {
|
if (existing?.organizationId !== orgId) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Member not found!",
|
||||||
body: {
|
|
||||||
message: "Member not found!",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await adapter.deleteMember(existing.id);
|
await adapter.deleteMember(existing.id);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { createAuthEndpoint } from "../../../api/call";
|
|||||||
import { generateId } from "../../../utils/id";
|
import { generateId } from "../../../utils/id";
|
||||||
import { getOrgAdapter } from "../adapter";
|
import { getOrgAdapter } from "../adapter";
|
||||||
import { orgMiddleware, orgSessionMiddleware } from "../call";
|
import { orgMiddleware, orgSessionMiddleware } from "../call";
|
||||||
|
import { APIError } from "better-call";
|
||||||
|
|
||||||
export const createOrganization = createAuthEndpoint(
|
export const createOrganization = createAuthEndpoint(
|
||||||
"/organization/create",
|
"/organization/create",
|
||||||
@@ -33,11 +34,8 @@ export const createOrganization = createAuthEndpoint(
|
|||||||
: options.allowUserToCreateOrganization;
|
: options.allowUserToCreateOrganization;
|
||||||
|
|
||||||
if (!canCreateOrg) {
|
if (!canCreateOrg) {
|
||||||
return ctx.json(null, {
|
throw new APIError("FORBIDDEN", {
|
||||||
status: 403,
|
message: "You are not allowed to create an organization",
|
||||||
body: {
|
|
||||||
message: "You are not allowed to create organizations",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const adapter = getOrgAdapter(ctx.context.adapter, options);
|
const adapter = getOrgAdapter(ctx.context.adapter, options);
|
||||||
@@ -51,11 +49,8 @@ export const createOrganization = createAuthEndpoint(
|
|||||||
: false;
|
: false;
|
||||||
|
|
||||||
if (hasReachedOrgLimit) {
|
if (hasReachedOrgLimit) {
|
||||||
return ctx.json(null, {
|
throw new APIError("FORBIDDEN", {
|
||||||
status: 403,
|
message: "You have reached the organization limit",
|
||||||
body: {
|
|
||||||
message: "You have reached the maximum number of organizations",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,11 +58,8 @@ export const createOrganization = createAuthEndpoint(
|
|||||||
ctx.body.slug,
|
ctx.body.slug,
|
||||||
);
|
);
|
||||||
if (existingOrganization) {
|
if (existingOrganization) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Organization with this slug already exists",
|
||||||
body: {
|
|
||||||
message: "Organization with this slug already exists",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const organization = await adapter.createOrganization({
|
const organization = await adapter.createOrganization({
|
||||||
@@ -104,8 +96,8 @@ export const updateOrganization = createAuthEndpoint(
|
|||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const session = await ctx.context.getSession(ctx);
|
const session = await ctx.context.getSession(ctx);
|
||||||
if (!session) {
|
if (!session) {
|
||||||
return ctx.json(null, {
|
throw new APIError("UNAUTHORIZED", {
|
||||||
status: 401,
|
message: "User not found",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const orgId = ctx.body.orgId || session.session.activeOrganizationId;
|
const orgId = ctx.body.orgId || session.session.activeOrganizationId;
|
||||||
@@ -207,11 +199,8 @@ export const deleteOrganization = createAuthEndpoint(
|
|||||||
organization: ["delete"],
|
organization: ["delete"],
|
||||||
});
|
});
|
||||||
if (canDeleteOrg.error) {
|
if (canDeleteOrg.error) {
|
||||||
return ctx.json(null, {
|
throw new APIError("FORBIDDEN", {
|
||||||
body: {
|
message: "You are not allowed to delete this organization",
|
||||||
message: "You are not allowed to delete this organization",
|
|
||||||
},
|
|
||||||
status: 403,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (orgId === session.session.activeOrganizationId) {
|
if (orgId === session.session.activeOrganizationId) {
|
||||||
@@ -239,11 +228,8 @@ export const getFullOrganization = createAuthEndpoint(
|
|||||||
const session = ctx.context.session;
|
const session = ctx.context.session;
|
||||||
const orgId = ctx.query.orgId || session.session.activeOrganizationId;
|
const orgId = ctx.query.orgId || session.session.activeOrganizationId;
|
||||||
if (!orgId) {
|
if (!orgId) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Organization not found",
|
||||||
body: {
|
|
||||||
message: "Organization id not found!",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const adapter = getOrgAdapter(ctx.context.adapter, ctx.context.orgOptions);
|
const adapter = getOrgAdapter(ctx.context.adapter, ctx.context.orgOptions);
|
||||||
@@ -252,11 +238,8 @@ export const getFullOrganization = createAuthEndpoint(
|
|||||||
ctx.context.db || undefined,
|
ctx.context.db || undefined,
|
||||||
);
|
);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 404,
|
message: "Organization not found",
|
||||||
body: {
|
|
||||||
message: "Organization not found!",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return ctx.json(organization);
|
return ctx.json(organization);
|
||||||
@@ -297,11 +280,8 @@ export const setActiveOrganization = createAuthEndpoint(
|
|||||||
});
|
});
|
||||||
if (!isMember) {
|
if (!isMember) {
|
||||||
await adapter.setActiveOrganization(session.session.id, null);
|
await adapter.setActiveOrganization(session.session.id, null);
|
||||||
return ctx.json(null, {
|
throw new APIError("FORBIDDEN", {
|
||||||
status: 400,
|
message: "You are not a member of this organization",
|
||||||
body: {
|
|
||||||
message: "You are not a member of this organization",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await adapter.setActiveOrganization(session.session.id, orgId);
|
await adapter.setActiveOrganization(session.session.id, orgId);
|
||||||
|
|||||||
@@ -264,12 +264,8 @@ export const passkey = (options?: PasskeyOptions) => {
|
|||||||
ctx.context.secret,
|
ctx.context.secret,
|
||||||
);
|
);
|
||||||
if (!challengeId) {
|
if (!challengeId) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Challenge not found",
|
||||||
statusText: "No challenge found",
|
|
||||||
body: {
|
|
||||||
message: "No challenge found",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,11 +331,8 @@ export const passkey = (options?: PasskeyOptions) => {
|
|||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
return ctx.json(null, {
|
throw new APIError("INTERNAL_SERVER_ERROR", {
|
||||||
status: 400,
|
message: "Failed to verify registration",
|
||||||
body: {
|
|
||||||
message: "Registration failed",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -355,8 +348,8 @@ export const passkey = (options?: PasskeyOptions) => {
|
|||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const origin = options?.origin || ctx.headers?.get("origin") || "";
|
const origin = options?.origin || ctx.headers?.get("origin") || "";
|
||||||
if (!origin) {
|
if (!origin) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "origin missing",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const resp = ctx.body.response;
|
const resp = ctx.body.response;
|
||||||
@@ -365,8 +358,8 @@ export const passkey = (options?: PasskeyOptions) => {
|
|||||||
ctx.context.secret,
|
ctx.context.secret,
|
||||||
);
|
);
|
||||||
if (!challengeId) {
|
if (!challengeId) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Challenge not found",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,8 +368,8 @@ export const passkey = (options?: PasskeyOptions) => {
|
|||||||
challengeId,
|
challengeId,
|
||||||
);
|
);
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Challenge not found",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const { expectedChallenge, callbackURL } = JSON.parse(
|
const { expectedChallenge, callbackURL } = JSON.parse(
|
||||||
@@ -392,11 +385,8 @@ export const passkey = (options?: PasskeyOptions) => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
if (!passkey) {
|
if (!passkey) {
|
||||||
return ctx.json(null, {
|
throw new APIError("UNAUTHORIZED", {
|
||||||
status: 401,
|
message: "Passkey not found",
|
||||||
body: {
|
|
||||||
message: "Passkey not found",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -418,11 +408,8 @@ export const passkey = (options?: PasskeyOptions) => {
|
|||||||
});
|
});
|
||||||
const { verified } = verification;
|
const { verified } = verification;
|
||||||
if (!verified)
|
if (!verified)
|
||||||
return ctx.json(null, {
|
throw new APIError("UNAUTHORIZED", {
|
||||||
status: 401,
|
message: "Authentication failed",
|
||||||
body: {
|
|
||||||
message: "verification failed",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await ctx.context.adapter.update<Passkey>({
|
await ctx.context.adapter.update<Passkey>({
|
||||||
@@ -442,11 +429,8 @@ export const passkey = (options?: PasskeyOptions) => {
|
|||||||
ctx.request,
|
ctx.request,
|
||||||
);
|
);
|
||||||
if (!s) {
|
if (!s) {
|
||||||
return ctx.json(null, {
|
throw new APIError("INTERNAL_SERVER_ERROR", {
|
||||||
status: 500,
|
message: "Unable to create session",
|
||||||
body: {
|
|
||||||
message: "Failed to create session",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await setSessionCookie(ctx, s.id);
|
await setSessionCookie(ctx, s.id);
|
||||||
@@ -467,11 +451,8 @@ export const passkey = (options?: PasskeyOptions) => {
|
|||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ctx.context.logger.error(e);
|
ctx.context.logger.error(e);
|
||||||
return ctx.json(null, {
|
throw new APIError("BAD_REQUEST", {
|
||||||
status: 400,
|
message: "Failed to verify authentication",
|
||||||
body: {
|
|
||||||
message: "Authentication failed",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -220,71 +220,66 @@ export const phoneNumber = (options?: {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const res = await signUpEmail({
|
try {
|
||||||
...ctx,
|
const res = await signUpEmail({
|
||||||
//@ts-expect-error
|
...ctx,
|
||||||
options: {
|
//@ts-expect-error
|
||||||
...ctx.context.options,
|
options: {
|
||||||
},
|
...ctx.context.options,
|
||||||
_flag: undefined,
|
|
||||||
});
|
|
||||||
if (res.error) {
|
|
||||||
return ctx.json(
|
|
||||||
{
|
|
||||||
user: null,
|
|
||||||
session: null,
|
|
||||||
},
|
},
|
||||||
{
|
_flag: undefined,
|
||||||
status: 400,
|
|
||||||
body: {
|
|
||||||
message: res.error.message,
|
|
||||||
status: 400,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (options?.otp?.sendOTPonSignUp) {
|
|
||||||
if (!options.otp.sendOTP) {
|
|
||||||
logger.warn("sendOTP not implemented");
|
|
||||||
throw new APIError("NOT_IMPLEMENTED", {
|
|
||||||
message: "sendOTP not implemented",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const code = generateOTP(options?.otp?.otpLength || 6);
|
|
||||||
await ctx.context.internalAdapter.createVerificationValue({
|
|
||||||
value: code,
|
|
||||||
identifier: ctx.body.phoneNumber,
|
|
||||||
expiresAt: getDate(opts.otp.expiresIn, "sec"),
|
|
||||||
});
|
});
|
||||||
await options.otp.sendOTP(ctx.body.phoneNumber, code);
|
|
||||||
}
|
|
||||||
|
|
||||||
const updated = await ctx.context.internalAdapter.updateUserByEmail(
|
if (options?.otp?.sendOTPonSignUp) {
|
||||||
res.user.email,
|
if (!options.otp.sendOTP) {
|
||||||
{
|
logger.warn("sendOTP not implemented");
|
||||||
[opts.phoneNumber]: ctx.body.phoneNumber,
|
throw new APIError("NOT_IMPLEMENTED", {
|
||||||
},
|
message: "sendOTP not implemented",
|
||||||
);
|
});
|
||||||
|
}
|
||||||
|
const code = generateOTP(options?.otp?.otpLength || 6);
|
||||||
|
await ctx.context.internalAdapter.createVerificationValue({
|
||||||
|
value: code,
|
||||||
|
identifier: ctx.body.phoneNumber,
|
||||||
|
expiresAt: getDate(opts.otp.expiresIn, "sec"),
|
||||||
|
});
|
||||||
|
await options.otp.sendOTP(ctx.body.phoneNumber, code);
|
||||||
|
}
|
||||||
|
|
||||||
if (ctx.body.callbackURL) {
|
const updated = await ctx.context.internalAdapter.updateUserByEmail(
|
||||||
return ctx.json(
|
res.user.email,
|
||||||
{
|
{
|
||||||
user: updated,
|
[opts.phoneNumber]: ctx.body.phoneNumber,
|
||||||
session: res.session,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
body: {
|
|
||||||
url: ctx.body.callbackURL,
|
|
||||||
redirect: true,
|
|
||||||
...res,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (ctx.body.callbackURL) {
|
||||||
|
return ctx.json(
|
||||||
|
{
|
||||||
|
user: updated,
|
||||||
|
session: res.session,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: {
|
||||||
|
url: ctx.body.callbackURL,
|
||||||
|
redirect: true,
|
||||||
|
...res,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ctx.json({
|
||||||
|
user: updated,
|
||||||
|
session: res.session,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof APIError) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
throw new APIError("INTERNAL_SERVER_ERROR", {
|
||||||
|
message: "Failed to create user",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return ctx.json({
|
|
||||||
user: updated,
|
|
||||||
session: res.session,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
sendVerificationCode: createAuthEndpoint(
|
sendVerificationCode: createAuthEndpoint(
|
||||||
@@ -338,31 +333,18 @@ export const phoneNumber = (options?: {
|
|||||||
if (!otp || otp.expiresAt < new Date()) {
|
if (!otp || otp.expiresAt < new Date()) {
|
||||||
if (otp && otp.expiresAt < new Date()) {
|
if (otp && otp.expiresAt < new Date()) {
|
||||||
await ctx.context.internalAdapter.deleteVerificationValue(otp.id);
|
await ctx.context.internalAdapter.deleteVerificationValue(otp.id);
|
||||||
|
throw new APIError("BAD_REQUEST", {
|
||||||
|
message: "OTP expired",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return ctx.json(
|
throw new APIError("BAD_REQUEST", {
|
||||||
{
|
message: "OTP not found",
|
||||||
status: false,
|
});
|
||||||
},
|
|
||||||
{
|
|
||||||
body: {
|
|
||||||
message: "Invalid code",
|
|
||||||
},
|
|
||||||
status: 400,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (otp.value !== ctx.body.code) {
|
if (otp.value !== ctx.body.code) {
|
||||||
return ctx.json(
|
throw new APIError("BAD_REQUEST", {
|
||||||
{
|
message: "Invalid OTP",
|
||||||
status: false,
|
});
|
||||||
},
|
|
||||||
{
|
|
||||||
body: {
|
|
||||||
message: "Invalid code",
|
|
||||||
},
|
|
||||||
status: 400,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
await ctx.context.internalAdapter.deleteVerificationValue(otp.id);
|
await ctx.context.internalAdapter.deleteVerificationValue(otp.id);
|
||||||
const user = await ctx.context.adapter.findOne<User>({
|
const user = await ctx.context.adapter.findOne<User>({
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { sessionMiddleware } from "../../../api";
|
|||||||
import { symmetricDecrypt, symmetricEncrypt } from "../../../crypto";
|
import { symmetricDecrypt, symmetricEncrypt } from "../../../crypto";
|
||||||
import { verifyTwoFactorMiddleware } from "../verify-middleware";
|
import { verifyTwoFactorMiddleware } from "../verify-middleware";
|
||||||
import type { TwoFactorProvider, UserWithTwoFactor } from "../types";
|
import type { TwoFactorProvider, UserWithTwoFactor } from "../types";
|
||||||
|
import { APIError } from "better-call";
|
||||||
|
|
||||||
export interface BackupCodeOptions {
|
export interface BackupCodeOptions {
|
||||||
/**
|
/**
|
||||||
@@ -98,12 +99,9 @@ export const backupCode2fa = (options?: BackupCodeOptions) => {
|
|||||||
ctx.context.secret,
|
ctx.context.secret,
|
||||||
);
|
);
|
||||||
if (!validate) {
|
if (!validate) {
|
||||||
return ctx.json(
|
throw new APIError("BAD_REQUEST", {
|
||||||
{ status: false },
|
message: "Invalid backup code",
|
||||||
{
|
});
|
||||||
status: 401,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return ctx.json({ status: true });
|
return ctx.json({ status: true });
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type { TwoFactorOptions, UserWithTwoFactor } from "./types";
|
|||||||
import type { Session } from "../../db/schema";
|
import type { Session } from "../../db/schema";
|
||||||
import { TWO_FACTOR_COOKIE_NAME, TRUST_DEVICE_COOKIE_NAME } from "./constant";
|
import { TWO_FACTOR_COOKIE_NAME, TRUST_DEVICE_COOKIE_NAME } from "./constant";
|
||||||
import { validatePassword } from "../../utils/password";
|
import { validatePassword } from "../../utils/password";
|
||||||
|
import { APIError } from "better-call";
|
||||||
|
|
||||||
export const twoFactor = (options?: TwoFactorOptions) => {
|
export const twoFactor = (options?: TwoFactorOptions) => {
|
||||||
const totp = totp2fa({
|
const totp = totp2fa({
|
||||||
@@ -42,15 +43,9 @@ export const twoFactor = (options?: TwoFactorOptions) => {
|
|||||||
userId: user.id,
|
userId: user.id,
|
||||||
});
|
});
|
||||||
if (!isPasswordValid) {
|
if (!isPasswordValid) {
|
||||||
return ctx.json(
|
throw new APIError("BAD_REQUEST", {
|
||||||
{ status: false },
|
message: "Invalid password",
|
||||||
{
|
});
|
||||||
status: 400,
|
|
||||||
body: {
|
|
||||||
message: "Invalid password",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const secret = generateRandomString(16, alphabet("a-z", "0-9", "-"));
|
const secret = generateRandomString(16, alphabet("a-z", "0-9", "-"));
|
||||||
const encryptedSecret = symmetricEncrypt({
|
const encryptedSecret = symmetricEncrypt({
|
||||||
@@ -95,15 +90,9 @@ export const twoFactor = (options?: TwoFactorOptions) => {
|
|||||||
userId: user.id,
|
userId: user.id,
|
||||||
});
|
});
|
||||||
if (!isPasswordValid) {
|
if (!isPasswordValid) {
|
||||||
return ctx.json(
|
throw new APIError("BAD_REQUEST", {
|
||||||
{ status: false },
|
message: "Invalid password",
|
||||||
{
|
});
|
||||||
status: 400,
|
|
||||||
body: {
|
|
||||||
message: "Invalid password",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
await ctx.context.adapter.update({
|
await ctx.context.adapter.update({
|
||||||
model: "user",
|
model: "user",
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ export const totp2fa = (options: TOTPOptions) => {
|
|||||||
}
|
}
|
||||||
const totp = new TOTPController(opts);
|
const totp = new TOTPController(opts);
|
||||||
const secret = Buffer.from(
|
const secret = Buffer.from(
|
||||||
await symmetricDecrypt({
|
symmetricDecrypt({
|
||||||
key: ctx.context.secret,
|
key: ctx.context.secret,
|
||||||
data: ctx.context.session.user.twoFactorSecret,
|
data: ctx.context.session.user.twoFactorSecret,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -111,15 +111,9 @@ export const verifyTwoFactorMiddleware = createAuthMiddleware(
|
|||||||
return ctx.json({ status: true });
|
return ctx.json({ status: true });
|
||||||
},
|
},
|
||||||
invalid: async () => {
|
invalid: async () => {
|
||||||
return ctx.json(
|
throw new APIError("UNAUTHORIZED", {
|
||||||
{ status: false },
|
message: "invalid two factor authentication",
|
||||||
{
|
});
|
||||||
status: 401,
|
|
||||||
body: {
|
|
||||||
message: "Invalid code",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
session: {
|
session: {
|
||||||
id: session.id,
|
id: session.id,
|
||||||
|
|||||||
5
studio/next-env.d.ts
vendored
5
studio/next-env.d.ts
vendored
@@ -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.
|
|
||||||
Reference in New Issue
Block a user