Skip to content

Commit

Permalink
feat: update db
Browse files Browse the repository at this point in the history
  • Loading branch information
alexandre-sarfati committed Apr 2, 2024
1 parent a1a7f21 commit 97b009e
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 114 deletions.
327 changes: 214 additions & 113 deletions apps/web/lib/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,59 @@ import {
} from "@dub/utils";
import { Link as LinkProps } from "@prisma/client";
import { createHash } from "crypto";
import { DefaultJWT, decode } from "next-auth/jwt";
import { getServerSession } from "next-auth/next";
import { exceededLimitError } from "../api/errors";
import { PlanProps, WorkspaceProps } from "../types";
import { ratelimit } from "../upstash";
import { authOptions } from "./options";

export interface UserJWT {
email: string;
id: string;
name: string;
image?: string;
}

export interface Session {
user: {
email: string;
id: string;
name: string;
image?: string;
};
user: UserJWT;
}

export interface CustomJWT extends DefaultJWT {
user?: UserJWT;
}

export type CallbackResponse = { error: Response } | { user: UserJWT };

export type AuthorizeResponse =
| {
headers: {};
error: Response;
user: null;
}
| {
headers: {};
error: null;
user: UserJWT;
};

function decodeJWT(token: string): Promise<UserJWT> {
return new Promise(async (resolve, reject) => {
try {
const result: CustomJWT | null = await decode({
token,
secret: process.env.NEXTAUTH_SECRET as string,
});

if (!result?.user) {
return reject("decode invalid");
}

resolve(result.user);
} catch (err) {
reject(err);
}
});
}

export const getSession = async () => {
Expand Down Expand Up @@ -97,7 +137,7 @@ export const withAuth = (
const searchParams = getSearchParams(req.url);
const { linkId } = params || {};

let apiKey: string | undefined = undefined;
let token: string | undefined = undefined;

const authorizationHeader = req.headers.get("Authorization");
if (authorizationHeader) {
Expand All @@ -109,7 +149,7 @@ export const withAuth = (
},
);
}
apiKey = authorizationHeader.replace("Bearer ", "");
token = authorizationHeader.replace("Bearer ", "");
}

const domain = params?.domain || searchParams.domain;
Expand All @@ -130,7 +170,7 @@ export const withAuth = (
// if there's no workspace ID or slug
if (!idOrSlug) {
// for /api/links (POST /api/links) – allow no session (but warn if user provides apiKey)
if (allowAnonymous && !apiKey) {
if (allowAnonymous && !token) {
// @ts-expect-error
return await handler({
req,
Expand All @@ -153,65 +193,96 @@ export const withAuth = (
slug = idOrSlug;
}

if (apiKey) {
const hashedKey = hashToken(apiKey, {
noSecret: true,
});

const user = await prisma.user.findFirst({
where: {
tokens: {
some: {
hashedKey,
if (token) {
try {
const decodedUser = await decodeJWT(token);
const user = await prisma.user.findFirst({
where: {
id: {
equals: decodedUser.id,
},
},
},
select: {
id: true,
name: true,
email: true,
},
});
if (!user) {
throw new DubApiError({
code: "unauthorized",
message: "Unauthorized: Invalid API key.",
select: {
id: true,
name: true,
email: true,
},
});
}
if (!user) {
throw new DubApiError({
code: "unauthorized",
message: "Unauthorized: Invalid Token.",
});
}
session = {
user: {
id: user.id,
name: user.name || "",
email: user.email || "",
},
};
} catch (e) {
const apiKey = token;

const { success, limit, reset, remaining } = await ratelimit(
10,
"1 s",
).limit(apiKey);
const hashedKey = hashToken(apiKey, {
noSecret: true,
});

headers = {
"Retry-After": reset.toString(),
"X-RateLimit-Limit": limit.toString(),
"X-RateLimit-Remaining": remaining.toString(),
"X-RateLimit-Reset": reset.toString(),
};
const user = await prisma.user.findFirst({
where: {
tokens: {
some: {
hashedKey,
},
},
},
select: {
id: true,
name: true,
email: true,
},
});
if (!user) {
throw new DubApiError({
code: "unauthorized",
message: "Unauthorized: Invalid API key.",
});
}

if (!success) {
throw new DubApiError({
code: "rate_limit_exceeded",
message: "Too many requests.",
const { success, limit, reset, remaining } = await ratelimit(
10,
"1 s",
).limit(apiKey);

headers = {
"Retry-After": reset.toString(),
"X-RateLimit-Limit": limit.toString(),
"X-RateLimit-Remaining": remaining.toString(),
"X-RateLimit-Reset": reset.toString(),
};

if (!success) {
throw new DubApiError({
code: "rate_limit_exceeded",
message: "Too many requests.",
});
}
await prisma.token.update({
where: {
hashedKey,
},
data: {
lastUsed: new Date(),
},
});
session = {
user: {
id: user.id,
name: user.name || "",
email: user.email || "",
},
};
}
await prisma.token.update({
where: {
hashedKey,
},
data: {
lastUsed: new Date(),
},
});
session = {
user: {
id: user.id,
name: user.name || "",
email: user.email || "",
},
};
} else {
session = await getSession();
if (!session?.user?.id) {
Expand Down Expand Up @@ -387,7 +458,7 @@ export const withAuth = (
const url = new URL(req.url || "", API_DOMAIN);
if (
workspace.plan === "free" &&
apiKey &&
token &&
url.pathname.includes("/analytics")
) {
throw new DubApiError({
Expand Down Expand Up @@ -471,66 +542,96 @@ export const withSession =
"Misconfigured authorization header. Did you forget to add 'Bearer '? Learn more: https://d.to/auth",
});
}
const apiKey = authorizationHeader.replace("Bearer ", "");
const token = authorizationHeader.replace("Bearer ", "");

const hashedKey = hashToken(apiKey, {
noSecret: true,
});
try {
const decodedUser = await decodeJWT(token);
const user = await prisma.user.findFirst({
where: {
id: {
equals: decodedUser.id,
},
},
select: {
id: true,
name: true,
email: true,
},
});
if (!user) {
throw new DubApiError({
code: "unauthorized",
message: "Unauthorized: Invalid Token.",
});
}
session = {
user: {
id: user.id,
name: user.name || "",
email: user.email || "",
},
};
} catch (e) {
const apiKey = token;
const hashedKey = hashToken(apiKey, {
noSecret: true,
});

const user = await prisma.user.findFirst({
where: {
tokens: {
some: {
hashedKey,
const user = await prisma.user.findFirst({
where: {
tokens: {
some: {
hashedKey,
},
},
},
},
select: {
id: true,
name: true,
email: true,
},
});
if (!user) {
throw new DubApiError({
code: "unauthorized",
message: "Unauthorized: Invalid API key.",
select: {
id: true,
name: true,
email: true,
},
});
}
if (!user) {
throw new DubApiError({
code: "unauthorized",
message: "Unauthorized: Invalid API key.",
});
}

const { success, limit, reset, remaining } = await ratelimit(
10,
"1 s",
).limit(apiKey);

headers = {
"Retry-After": reset.toString(),
"X-RateLimit-Limit": limit.toString(),
"X-RateLimit-Remaining": remaining.toString(),
"X-RateLimit-Reset": reset.toString(),
};

if (!success) {
return new Response("Too many requests.", {
status: 429,
headers,
const { success, limit, reset, remaining } = await ratelimit(
10,
"1 s",
).limit(apiKey);

headers = {
"Retry-After": reset.toString(),
"X-RateLimit-Limit": limit.toString(),
"X-RateLimit-Remaining": remaining.toString(),
"X-RateLimit-Reset": reset.toString(),
};

if (!success) {
return new Response("Too many requests.", {
status: 429,
headers,
});
}
await prisma.token.update({
where: {
hashedKey,
},
data: {
lastUsed: new Date(),
},
});
session = {
user: {
id: user.id,
name: user.name || "",
email: user.email || "",
},
};
}
await prisma.token.update({
where: {
hashedKey,
},
data: {
lastUsed: new Date(),
},
});
session = {
user: {
id: user.id,
name: user.name || "",
email: user.email || "",
},
};
} else {
session = await getSession();
if (!session?.user.id) {
Expand Down
Loading

0 comments on commit 97b009e

Please sign in to comment.