Skip to content

Commit

Permalink
move away from supabase auth only and use keyhippo for api key auth (#28
Browse files Browse the repository at this point in the history
)
  • Loading branch information
tomasfrancisco authored Sep 10, 2024
1 parent 77d4931 commit 22a30d1
Show file tree
Hide file tree
Showing 17 changed files with 129 additions and 229 deletions.
26 changes: 24 additions & 2 deletions apps/dashboard/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,33 @@ const nextConfig = {
},
{
key: 'Access-Control-Allow-Origin',
value: '*', // TODO: Perhaps set figma origin instead?
value: '*', // TODO: Set specific origin instead of '*' for production
},
{
key: 'Access-Control-Allow-Methods',
value: 'POST, PUT, DELETE, OPTIONS',
value: 'GET, POST, PUT, DELETE, OPTIONS',
},
{
key: 'Access-Control-Allow-Headers',
value: 'Content-Type, Authorization',
},
],
},
// Add this new object to cover /api/auth paths
{
source: '/api/auth/:path*',
headers: [
{
key: 'Access-Control-Allow-Credentials',
value: 'true',
},
{
key: 'Access-Control-Allow-Origin',
value: '*', // TODO: Set specific origin instead of '*' for production
},
{
key: 'Access-Control-Allow-Methods',
value: 'GET, POST, PUT, DELETE, OPTIONS',
},
{
key: 'Access-Control-Allow-Headers',
Expand Down
22 changes: 0 additions & 22 deletions apps/dashboard/src/app/api/auth/refresh/route.ts

This file was deleted.

47 changes: 25 additions & 22 deletions apps/dashboard/src/lib/middleware/figma/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { MiddlewareFactory } from '../compose';
import { createMiddlewareClient } from '@ds-project/auth/middleware';
import { clientEnv } from '@/env/client';
import { config } from '@/config';
import { createApiKey } from '@ds-project/api/operations';

export const figmaMiddleware: MiddlewareFactory =
(middleware) =>
Expand All @@ -31,24 +32,25 @@ export const figmaMiddleware: MiddlewareFactory =
supabaseUrl: clientEnv.NEXT_PUBLIC_SUPABASE_URL,
});
const {
data: { session },
} = await supabase.auth.getSession();
const accessToken = session?.access_token;
const refreshToken = session?.refresh_token;
const expiresAt = session?.expires_at;

if (accessToken) {
console.log('🔐 Figma: User is authenticated. Exchanging key...');
data: { user },
} = await supabase.auth.getUser();

if (user) {
console.log('🔐 Figma: User is authenticated. Exchanging api key...');
const keyValue = await kv.getdel<KVCredentialsRead>(figmaKey);

if (keyValue) {
const apiKey = await createApiKey({
supabase,
userId: user.id,
description: `Figma API Key - ${new Date().toISOString()}`,
});
if (
accessToken &&
refreshToken &&
expiresAt &&
apiKey.status === 'success' &&
apiKey.apiKey &&
(await kv.set<KVCredentials>(
keyValue.readKey,
{ accessToken, refreshToken, expiresAt },
{ apiKey: apiKey.apiKey },
{
px: 5 * 60 * 1000, // Set the 5 minutes expire time, in milliseconds (a positive integer).
nx: true, // Only set the key if it does not already exist.
Expand Down Expand Up @@ -88,27 +90,28 @@ export const figmaMiddleware: MiddlewareFactory =
supabaseUrl: clientEnv.NEXT_PUBLIC_SUPABASE_URL,
});
const {
data: { session },
} = await supabase.auth.getSession();
const accessToken = session?.access_token;
const refreshToken = session?.refresh_token;
const expiresAt = session?.expires_at;
data: { user },
} = await supabase.auth.getUser();

if (!accessToken) {
if (!user) {
console.log('🔐 Figma: User is not authenticated. Skipping.');
return middleware(request, event, response);
}

const keyValue = await kv.getdel<KVCredentialsRead>(figmaKey);

if (keyValue) {
const apiKey = await createApiKey({
supabase,
userId: user.id,
description: `Figma API Key - ${new Date().toISOString()}`,
});
if (
accessToken &&
refreshToken &&
expiresAt &&
apiKey.status === 'success' &&
apiKey.apiKey &&
(await kv.set<KVCredentials>(
keyValue.readKey,
{ accessToken, refreshToken, expiresAt },
{ apiKey: apiKey.apiKey },
{
px: 5 * 60 * 1000, // Set the 5 minutes expire time, in milliseconds (a positive integer).
nx: true, // Only set the key if it does not already exist.
Expand Down
4 changes: 1 addition & 3 deletions apps/dashboard/src/types/kv-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ export interface KVCredentialsRead {
}

export interface KVCredentials {
accessToken: string;
refreshToken: string;
expiresAt: number;
apiKey: string;
}

export interface KVOAuthState {
Expand Down
14 changes: 9 additions & 5 deletions packages/api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"name": "@ds-project/api",
"private": true,
"keywords": [],
"license": "ISC",
"author": "",
"type": "module",
"exports": {
".": {
Expand All @@ -14,17 +17,18 @@
"./react": {
"types": "./dist/react.d.ts",
"default": "./src/react.tsx"
},
"./operations": {
"types": "./dist/operations.d.ts",
"default": "./src/operations/index.ts"
}
},
"prettier": "@ds-project/prettier",
"scripts": {
"lint": "eslint",
"format": "prettier --check . --ignore-path ../../.gitignore --ignore-path ../../.prettierignore",
"lint": "eslint",
"type-check": "tsc --noEmit --emitDeclarationOnly false"
},
"keywords": [],
"author": "",
"license": "ISC",
"prettier": "@ds-project/prettier",
"dependencies": {
"@ds-project/auth": "workspace:*",
"@ds-project/database": "workspace:*",
Expand Down
16 changes: 16 additions & 0 deletions packages/api/src/operations/create-api-key.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { createServerClient } from '@ds-project/auth/server';
import type { Database } from '@ds-project/database';
import { KeyHippo } from 'keyhippo';

export async function createApiKey({
supabase,
userId,
description,
}: {
supabase: ReturnType<typeof createServerClient<Database>>;
userId: string;
description: string;
}): Promise<Awaited<ReturnType<typeof KeyHippo.prototype.createApiKey>>> {
const keyHippo = new KeyHippo(supabase);
return await keyHippo.createApiKey(userId, description);
}
2 changes: 2 additions & 0 deletions packages/api/src/operations/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './create-api-key';
export * from './release';
8 changes: 6 additions & 2 deletions packages/api/src/router/api-keys.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { z } from 'zod';
import { createTRPCRouter, protectedProcedure } from '../trpc';
import { KeyHippo } from 'keyhippo';
import { createApiKey } from '../operations/create-api-key';

export const apiKeysRouter = createTRPCRouter({
list: protectedProcedure
Expand All @@ -27,8 +28,11 @@ export const apiKeysRouter = createTRPCRouter({
})
)
.mutation(async ({ ctx, input }) => {
const keyHippo = new KeyHippo(ctx.supabase);
return await keyHippo.createApiKey(ctx.account.userId, input.description);
return createApiKey({
supabase: ctx.supabase,
userId: ctx.account.userId,
description: input.description,
});
}),

revoke: protectedProcedure
Expand Down
4 changes: 2 additions & 2 deletions packages/api/src/router/projects.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { eq } from '@ds-project/database';

import { createTRPCRouter, protectedProcedure } from '../trpc';
import { apiProcedure, createTRPCRouter, protectedProcedure } from '../trpc';
import { AccountsToProjects, Projects } from '@ds-project/database/schema';

export const projectsRouter = createTRPCRouter({
Expand All @@ -15,7 +15,7 @@ export const projectsRouter = createTRPCRouter({
});
}),

account: protectedProcedure.query(async ({ ctx }) => {
account: apiProcedure.query(async ({ ctx }) => {
return ctx.database
.select({
id: Projects.id,
Expand Down
4 changes: 2 additions & 2 deletions packages/api/src/router/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { z } from 'zod';

import { eq } from '@ds-project/database';

import { createTRPCRouter, protectedProcedure } from '../trpc';
import { apiProcedure, createTRPCRouter, protectedProcedure } from '../trpc';
import {
InsertResourcesSchema,
PreprocessedTokensSchema,
Expand Down Expand Up @@ -47,7 +47,7 @@ export const resourcesRouter = createTRPCRouter({
return ctx.database.insert(Resources).values(input);
}),

updateDesignTokens: protectedProcedure
updateDesignTokens: apiProcedure
.input(
z.object({
name: z.string(),
Expand Down
52 changes: 29 additions & 23 deletions packages/api/src/trpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import { initTRPC, TRPCError } from '@trpc/server';
import SuperJSON from 'superjson';
import { ZodError } from 'zod';
import { createServerClient, validateToken } from '@ds-project/auth/server';
import { createServerClient } from '@ds-project/auth/server';

// import type { Session } from '@acme/auth';
// import { auth, validateToken } from '@acme/auth';
Expand All @@ -18,21 +18,7 @@ import { eq } from '@ds-project/database';
import type { Account } from '@ds-project/database/schema';
import type { Database } from '@ds-project/database';
import type { DSContext } from './types/context';

/**
* Isomorphic Session getter for API requests
* - Expo requests will have a session token in the Authorization header
* - Next.js requests will have a session token in cookies
*/
async function isomorphicGetUser(authToken: string | null) {
if (authToken) return validateToken<Database>(authToken);

const authClient = createServerClient<Database>();
const {
data: { user },
} = await authClient.auth.getUser();
return user;
}
import { KeyHippo } from 'keyhippo';

/**
* 1. CONTEXT
Expand All @@ -51,23 +37,28 @@ export const createTRPCContext = async (opts: {
account: Account | null;
}) => {
const supabase = createServerClient<Database>();
const token = opts.headers.get('Authorization') ?? null;
const user = await isomorphicGetUser(token);
const keyHippo = new KeyHippo(supabase);
const { userId } = await keyHippo.authenticate({
headers: opts.headers,
} as Request);

const source = opts.headers.get('x-trpc-source') ?? 'unknown';
console.log(`>>> tRPC Request from ${source} by ${user?.id}`);
console.log(`>>> tRPC Request from ${source} by ${userId}`);

const account = user?.id
const account = userId
? ((await database.query.Accounts.findFirst({
where: (accounts) => eq(accounts.userId, user.id),
where: (accounts) => eq(accounts.userId, userId),
})) ?? null)
: null;

const {
data: { user },
} = await supabase.auth.getUser();

return {
supabase,
user,
database,
token,
user,
account,
};
};
Expand Down Expand Up @@ -163,3 +154,18 @@ export const protectedProcedure = t.procedure
} as DSContext,
});
});

export const apiProcedure = t.procedure
.use(timingMiddleware)
.use(({ ctx, next }) => {
if (!ctx.account) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}

return next({
ctx: {
...ctx,
account: ctx.account,
} as DSContext,
});
});
16 changes: 8 additions & 8 deletions packages/auth/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"name": "@ds-project/auth",
"private": true,
"keywords": [],
"license": "ISC",
"author": "",
"type": "module",
"exports": {
"./client": {
Expand All @@ -16,18 +19,12 @@
"default": "./src/server/index.ts"
}
},
"prettier": "@ds-project/prettier",
"scripts": {
"lint": "eslint",
"format": "prettier --check . --ignore-path ../../.gitignore --ignore-path ../../.prettierignore",
"lint": "eslint",
"type-check": "tsc --noEmit --emitDeclarationOnly false"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependecies": {
"@ds-project/database": "workspace:*"
},
"prettier": "@ds-project/prettier",
"dependencies": {
"@next/env": "^14.2.5",
"@supabase/ssr": "^0.4.0",
Expand All @@ -44,5 +41,8 @@
"eslint": "catalog:",
"prettier": "catalog:",
"typescript": "catalog:"
},
"dependecies": {
"@ds-project/database": "workspace:*"
}
}
Loading

0 comments on commit 22a30d1

Please sign in to comment.