Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
Yash-1511 authored Oct 21, 2024
2 parents ff5a125 + 39b236f commit 469d548
Show file tree
Hide file tree
Showing 16 changed files with 95 additions and 23 deletions.
5 changes: 0 additions & 5 deletions .changeset/late-colts-shout.md

This file was deleted.

6 changes: 6 additions & 0 deletions apps/api/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# api

## 0.1.0

### Minor Changes

- 09d36ad: add /v1/keys.whoami route

## 0.0.14

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "api",
"version": "0.0.14",
"version": "0.1.0",
"private": true,
"scripts": {
"build": "tsc",
Expand Down
16 changes: 13 additions & 3 deletions apps/api/src/pkg/keys/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ type InvalidResponse = {
| "DISABLED"
| "INSUFFICIENT_PERMISSIONS";
key: Key;
identity: { id: string; externalId: string; meta: Record<string, unknown> | null } | null;
identity: {
id: string;
externalId: string;
meta: Record<string, unknown> | null;
} | null;
api: Api;
ratelimit?: {
remaining: number;
Expand All @@ -73,7 +77,11 @@ type ValidResponse = {
code?: never;
valid: true;
key: Key;
identity: { id: string; externalId: string; meta: Record<string, unknown> | null } | null;
identity: {
id: string;
externalId: string;
meta: Record<string, unknown> | null;
} | null;
api: Api;
ratelimit?: {
remaining: number;
Expand Down Expand Up @@ -451,6 +459,7 @@ export class KeyService {

if (data.api.ipWhitelist) {
const ip = c.req.header("True-Client-IP") ?? c.req.header("CF-Connecting-IP");

if (!ip) {
return Ok({
key: data.key,
Expand All @@ -461,7 +470,8 @@ export class KeyService {
permissions: data.permissions,
});
}
const ipWhitelist = JSON.parse(data.api.ipWhitelist) as string[];

const ipWhitelist = data.api.ipWhitelist.split(",").map((s) => s.trim());
if (!ipWhitelist.includes(ip)) {
return Ok({
key: data.key,
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/routes/legacy_keys_verifyKey.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ describe("with ip whitelist", () => {
name: "test",
authType: "key",
keyAuthId: keyAuthId,
ipWhitelist: JSON.stringify(["100.100.100.100"]),
ipWhitelist: ["100.100.100.100"].join(","),
createdAt: new Date(),
deletedAt: null,
});
Expand Down Expand Up @@ -177,7 +177,7 @@ describe("with ip whitelist", () => {
name: "test",
authType: "key",
keyAuthId: keyAuthid,
ipWhitelist: JSON.stringify(["100.100.100.100"]),
ipWhitelist: ["100.100.100.100"].join(","),
createdAt: new Date(),
deletedAt: null,
});
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/routes/v1_apis_getApi.happy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ test("with ip whitelist", async (t) => {
id: newId("api"),
name: "with ip whitelist",
workspaceId: h.resources.userWorkspace.id,
ipWhitelist: JSON.stringify(["127.0.0.1"]),
ipWhitelist: ["127.0.0.1"].join(","),
createdAt: new Date(),
deletedAt: null,
};
Expand Down
5 changes: 3 additions & 2 deletions apps/api/src/routes/v1_keys_verifyKey.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ describe("when ratelimited", () => {
expect(res.body.identity!.externalId).toEqual(externalId);
});
});

describe("with ratelimit override", () => {
test("deducts the correct number of tokens", { timeout: 20000 }, async (t) => {
const h = await IntegrationHarness.init(t);
Expand Down Expand Up @@ -517,7 +518,7 @@ describe("with ip whitelist", () => {
name: "test",
authType: "key",
keyAuthId: keyAuthId,
ipWhitelist: JSON.stringify(["100.100.100.100"]),
ipWhitelist: ["100.100.100.100"].join(","),
createdAt: new Date(),
});

Expand Down Expand Up @@ -565,7 +566,7 @@ describe("with ip whitelist", () => {
name: "test",
authType: "key",
keyAuthId: keyAuthid,
ipWhitelist: JSON.stringify(["100.100.100.100"]),
ipWhitelist: ["100.100.100.100"].join(","),
createdAt: new Date(),
});

Expand Down
1 change: 1 addition & 0 deletions apps/dashboard/app/(app)/apis/[apiId]/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default async function SettingsPage(props: Props) {
if (!workspace || workspace.tenantId !== tenantId) {
return redirect("/new");
}

const api = workspace.apis.find((api) => api.id === props.params.apiId);
if (!api) {
return notFound();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const formSchema = z.object({

type Props = {
workspace: {
plan: Workspace["plan"];
features: Workspace["features"];
};
api: {
id: string;
Expand All @@ -42,7 +42,7 @@ type Props = {

export const UpdateIpWhitelist: React.FC<Props> = ({ api, workspace }) => {
const router = useRouter();
const isEnabled = workspace.plan === "enterprise";
const isEnabled = workspace.features.ipWhitelist;

const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
Expand Down Expand Up @@ -79,7 +79,7 @@ export const UpdateIpWhitelist: React.FC<Props> = ({ api, workspace }) => {
</CardDescription>
</CardHeader>
<CardContent>
{workspace.plan === "enterprise" ? (
{isEnabled ? (
<div className="flex flex-col space-y-2">
<input type="hidden" name="workspaceId" value={api.workspaceId} />
<input type="hidden" name="apiId" value={api.id} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ import { useForm } from "react-hook-form";
import { z } from "zod";

export const dynamic = "force-dynamic";

const validCharactersRegex = /^[a-zA-Z0-9-_]+$/;

const formSchema = z.object({
workspaceId: z.string(),
name: z.string(),
name: z.string().min(3).regex(validCharactersRegex, {
message: "Workspace can only contain letters, numbers, dashes, and underscores",
}),
});

type Props = {
Expand Down Expand Up @@ -53,7 +58,7 @@ export const UpdateWorkspaceName: React.FC<Props> = ({ workspace }) => {
async function onSubmit(values: z.infer<typeof formSchema>) {
await updateName.mutateAsync(values);
}

const isDisabled = form.formState.isLoading || !form.formState.isValid || updateName.isLoading;
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
Expand Down Expand Up @@ -84,7 +89,7 @@ export const UpdateWorkspaceName: React.FC<Props> = ({ workspace }) => {
<Button
variant={updateName.isLoading ? "disabled" : "primary"}
type="submit"
disabled={updateName.isLoading}
disabled={isDisabled}
>
{updateName.isLoading ? <Loading /> : "Save"}
</Button>
Expand Down
16 changes: 15 additions & 1 deletion apps/dashboard/lib/trpc/routers/api/updateIpWhitelist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,27 @@ export const updateApiIpWhitelist = rateLimitedProcedure(ratelimit.update)
"We are unable to update the API whitelist. Please try again or contact support@unkey.dev",
});
});
if (!api || api.workspace.tenantId !== ctx.tenant.id) {

if (
!api ||
api.workspace.tenantId !== ctx.tenant.id ||
input.workspaceId !== api.workspace.id
) {
throw new TRPCError({
code: "NOT_FOUND",
message:
"We are unable to find the correct API. Please try again or contact support@unkey.dev.",
});
}

if (!api.workspace.features.ipWhitelist) {
throw new TRPCError({
code: "FORBIDDEN",
message:
"IP Whitelisting is only available for enterprise plans. Please contact support@unkey.dev.",
});
}

const newIpWhitelist = input.ipWhitelist === null ? null : input.ipWhitelist.join(",");

await db
Expand All @@ -68,6 +81,7 @@ export const updateApiIpWhitelist = rateLimitedProcedure(ratelimit.update)
"We are unable to update the API whitelist. Please try again or contact support@unkey.dev",
});
});

await insertAuditLogs(tx, {
workspaceId: api.workspace.id,
actor: {
Expand Down
2 changes: 1 addition & 1 deletion apps/www/app/templates/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type Props = {
};
};

export const revalidate = 300; // 5min
export const revalidate = 3600; // 1 hour

export async function generateStaticParams() {
return Object.keys(templates).map((slug) => ({
Expand Down
42 changes: 41 additions & 1 deletion apps/www/app/templates/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export const frameworks = [
"Oak",
"FastAPI",
"NestJS",
"Koa",
"AdonisJS",
] as const;
export type Framework = StrArrayToUnion<typeof frameworks>;
// id -> label
Expand Down Expand Up @@ -55,6 +57,43 @@ export type Template = {
};

export const templates: Record<string, Template> = {
"adonis-ratelimit": {
title: "Ratelimiting in AdonisJS apps",
description: "Dynami IP based ratelimiting.",
authors: ["Ionfinisher"],
repository: "https://github.com/Ionfinisher/unkey-adonisjs-ratelimit",

image: "/images/templates/adonis-ratelimit.png",
readmeUrl:
"https://raw.githubusercontent.com/Ionfinisher/unkey-adonisjs-ratelimit/refs/heads/main/README.md",

language: "Typescript",
framework: "AdonisJS",
},
sealshare: {
title: "End-to-end encrypted secret sharing",
description: "Share secrets securely, directly in your browser.",
authors: ["unrenamed"],
repository: "https://github.com/unrenamed/sealshare",

image: "/images/templates/sealshare.png",
readmeUrl: "https://raw.githubusercontent.com/unrenamed/sealshare/refs/heads/main/README.md",

language: "Typescript",
framework: "Next.js",
},
koa: {
title: "Koa.js middleware with Unkey RBAC",
description: "Implement API key verification in your Koa apps",
authors: ["harshsbhat"],
repository: "https://github.com/harshsbhat/unkey-koa",

image: "/images/templates/koa.png",
readmeUrl: "https://raw.githubusercontent.com/harshsbhat/unkey-koa/refs/heads/main/README.md",

language: "Typescript",
framework: "Koa",
},
"nextjs-supabase-payasyougo": {
title: "Next.js Pay-as-you-Go starter kit",
description: "Building Pay-As-You-Go apps with Next.js, Unkey and Supabase",
Expand All @@ -63,7 +102,8 @@ export const templates: Record<string, Template> = {

image: "/images/templates/nextjs-supabase-payasyougo.png",
readmeUrl:
"https://raw.githubusercontent.com/unrenamed/unkey-nextjs-pay-as-you-go/heads/main/README.md",
"https://raw.githubusercontent.com/unrenamed/unkey-nextjs-pay-as-you-go/refs/heads/main/README.md",

language: "Typescript",
framework: "Next.js",
},
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/www/public/images/templates/koa.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/www/public/images/templates/sealshare.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 469d548

Please sign in to comment.