diff --git a/apps/web/src/app/(dashboard)/settings/token/page.tsx b/apps/web/src/app/(dashboard)/settings/token/page.tsx index ab4992f14..889e7a2a8 100644 --- a/apps/web/src/app/(dashboard)/settings/token/page.tsx +++ b/apps/web/src/app/(dashboard)/settings/token/page.tsx @@ -3,7 +3,7 @@ import { ApiTokenForm } from '~/components/forms/token'; export default function ApiToken() { return (
-

API Token

+

API Tokens

On this page, you can create new API tokens and manage the existing ones. diff --git a/apps/web/src/components/forms/token.tsx b/apps/web/src/components/forms/token.tsx index 954d4dd2e..f3f05028e 100644 --- a/apps/web/src/components/forms/token.tsx +++ b/apps/web/src/components/forms/token.tsx @@ -6,6 +6,7 @@ import { useRouter } from 'next/navigation'; import { zodResolver } from '@hookform/resolvers/zod'; import { Loader } from 'lucide-react'; +import { DateTime } from 'luxon'; import { useForm } from 'react-hook-form'; import type { z } from 'zod'; @@ -139,23 +140,13 @@ export const ApiTokenForm = ({ className }: ApiTokenFormProps) => {

Created:{' '} {token.createdAt - ? new Date(token.createdAt).toLocaleDateString(undefined, { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - }) + ? DateTime.fromJSDate(token.createdAt).toLocaleString(DateTime.DATETIME_FULL) : 'N/A'}

Expires:{' '} {token.expires - ? new Date(token.expires).toLocaleDateString(undefined, { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - }) + ? DateTime.fromJSDate(token.createdAt).toLocaleString(DateTime.DATETIME_FULL) : 'N/A'}

{ - try { - return await checkUserFromToken({ token }); - } catch (e) { - return null; - } -}; - const router = createNextRoute(contract, { getDocuments: async (args) => { const page = Number(args.query.page) || 1; const perPage = Number(args.query.perPage) || 10; const { authorization } = args.headers; + let user; - const user = await validateUserToken(authorization); - - if (!user) { + try { + user = await checkUserFromToken({ token: authorization }); + } catch (e) { return { status: 401, body: { - message: 'Unauthorized', + message: e.message, }, }; } @@ -50,14 +43,15 @@ const router = createNextRoute(contract, { getDocument: async (args) => { const { id: documentId } = args.params; const { authorization } = args.headers; + let user; - const user = await validateUserToken(authorization); - - if (!user) { + try { + user = await checkUserFromToken({ token: authorization }); + } catch (e) { return { status: 401, body: { - message: 'Unauthorized', + message: e.message, }, }; } @@ -82,13 +76,15 @@ const router = createNextRoute(contract, { const { id: documentId } = args.params; const { authorization } = args.headers; - const user = await validateUserToken(authorization); + let user; - if (!user) { + try { + user = await checkUserFromToken({ token: authorization }); + } catch (e) { return { status: 401, body: { - message: 'Unauthorized', + message: e.message, }, }; } @@ -141,14 +137,15 @@ const router = createNextRoute(contract, { const { authorization } = args.headers; const { id } = args.params; const { body } = args; + let user; - const user = await validateUserToken(authorization); - - if (!user) { + try { + user = await checkUserFromToken({ token: authorization }); + } catch (e) { return { status: 401, body: { - message: 'Unauthorized', + message: e.message, }, }; } diff --git a/packages/lib/constants/time.ts b/packages/lib/constants/time.ts index e2581e14c..c1a262048 100644 --- a/packages/lib/constants/time.ts +++ b/packages/lib/constants/time.ts @@ -3,3 +3,5 @@ export const ONE_MINUTE = ONE_SECOND * 60; export const ONE_HOUR = ONE_MINUTE * 60; export const ONE_DAY = ONE_HOUR * 24; export const ONE_WEEK = ONE_DAY * 7; +export const ONE_MONTH = ONE_DAY * 30; +export const ONE_YEAR = ONE_DAY * 365; diff --git a/packages/lib/server-only/public-api/create-api-token.ts b/packages/lib/server-only/public-api/create-api-token.ts index 582053359..645e9fb1b 100644 --- a/packages/lib/server-only/public-api/create-api-token.ts +++ b/packages/lib/server-only/public-api/create-api-token.ts @@ -3,7 +3,7 @@ import crypto from 'crypto'; import { prisma } from '@documenso/prisma'; // temporary choice for testing only -import { ONE_WEEK } from '../../constants/time'; +import { ONE_YEAR } from '../../constants/time'; type CreateApiTokenInput = { userId: number; @@ -22,7 +22,7 @@ export const createApiToken = async ({ userId, tokenName }: CreateApiTokenInput) token: tokenHash, name: tokenName, userId, - expires: new Date(Date.now() + ONE_WEEK), + expires: new Date(Date.now() + ONE_YEAR), }, }); diff --git a/packages/lib/server-only/public-api/get-user-by-token.ts b/packages/lib/server-only/public-api/get-user-by-token.ts index 3092deaa7..277fc13b2 100644 --- a/packages/lib/server-only/public-api/get-user-by-token.ts +++ b/packages/lib/server-only/public-api/get-user-by-token.ts @@ -1,7 +1,7 @@ import { prisma } from '@documenso/prisma'; export const checkUserFromToken = async ({ token }: { token: string }) => { - const user = await prisma.user.findFirstOrThrow({ + const user = await prisma.user.findFirst({ where: { ApiToken: { some: { @@ -9,7 +9,20 @@ export const checkUserFromToken = async ({ token }: { token: string }) => { }, }, }, + include: { + ApiToken: true, + }, }); + if (!user) { + throw new Error('Token not found'); + } + + const tokenObject = user.ApiToken.find((apiToken) => apiToken.token === token); + + if (!tokenObject || new Date(tokenObject.expires) < new Date()) { + throw new Error('The API token has expired'); + } + return user; };