Skip to content

Commit 2c74669

Browse files
committed
example app
1 parent c9c3e06 commit 2c74669

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+9028
-91
lines changed

biome.jsonc

Lines changed: 43 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,45 @@
11
{
2-
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
3-
"extends": ["ultracite"],
4-
"files": {
5-
"includes": ["!benchmark.ts"]
6-
},
7-
"formatter": {
8-
"enabled": true,
9-
"indentWidth": 2,
10-
"indentStyle": "tab"
11-
},
12-
"linter": {
13-
"rules": {
14-
"complexity": {
15-
"noExcessiveCognitiveComplexity": {
16-
"level": "warn",
17-
"options": {
18-
"maxAllowedComplexity": 50
19-
}
20-
}
21-
}
22-
}
23-
},
24-
"overrides": [
25-
{
26-
"linter": {
27-
"rules": {
28-
"suspicious": {
29-
"noConsole": "off",
30-
"noExplicitAny": "off"
31-
},
32-
"style": {
33-
"noMagicNumbers": "off"
34-
},
35-
"complexity": {
36-
"useLiteralKeys": "off"
37-
},
38-
"correctness": {
39-
"noUnusedVariables": "off"
40-
}
41-
}
42-
}
43-
}
44-
]
2+
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
3+
"extends": ["ultracite"],
4+
"files": {
5+
"includes": ["!benchmark.ts"]
6+
},
7+
"formatter": {
8+
"enabled": true,
9+
"indentWidth": 2,
10+
"indentStyle": "tab"
11+
},
12+
"linter": {
13+
"rules": {
14+
"complexity": {
15+
"noExcessiveCognitiveComplexity": {
16+
"level": "warn",
17+
"options": {
18+
"maxAllowedComplexity": 50
19+
}
20+
}
21+
}
22+
}
23+
},
24+
"overrides": [
25+
{
26+
"linter": {
27+
"rules": {
28+
"suspicious": {
29+
"noConsole": "off",
30+
"noExplicitAny": "off"
31+
},
32+
"style": {
33+
"noMagicNumbers": "off"
34+
},
35+
"complexity": {
36+
"useLiteralKeys": "off"
37+
},
38+
"correctness": {
39+
"noUnusedVariables": "off"
40+
}
41+
}
42+
}
43+
}
44+
]
4545
}

bun.lock

Lines changed: 13 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/.gitignore

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.*
7+
.yarn/*
8+
!.yarn/patches
9+
!.yarn/plugins
10+
!.yarn/releases
11+
!.yarn/versions
12+
13+
# testing
14+
/coverage
15+
16+
# next.js
17+
/.next/
18+
/out/
19+
20+
# production
21+
/build
22+
23+
# misc
24+
.DS_Store
25+
*.pem
26+
27+
# debug
28+
npm-debug.log*
29+
yarn-debug.log*
30+
yarn-error.log*
31+
.pnpm-debug.log*
32+
33+
# env files (can opt-in for committing if needed)
34+
.env*
35+
36+
# vercel
37+
.vercel
38+
39+
# typescript
40+
*.tsbuildinfo
41+
next-env.d.ts

example/README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Keypal Example App
2+
3+
A comprehensive Next.js example application demonstrating [keypal](https://github.com/izadoesdev/keypal) - a TypeScript library for secure API key management.
4+
5+
## Features Demonstrated
6+
7+
This example showcases all major features of keypal:
8+
9+
- **API Key Management**: Create, list, enable/disable, rotate, and revoke API keys
10+
- **Key Verification**: Automatic key extraction from headers and verification
11+
- **Scope-based Permissions**: Fine-grained access control with scopes
12+
- **Usage Tracking**: Monitor when and how keys are used
13+
- **Audit Logging**: Track all key operations with full context
14+
- **Multiple Storage Backends**: Memory, Redis, and Drizzle ORM adapters
15+
- **Resource-level Permissions**: Granular permissions for specific resources
16+
17+
## Getting Started
18+
19+
### Prerequisites
20+
21+
- [Bun](https://bun.sh) (recommended) or Node.js 18+
22+
- Redis (optional, for Redis storage example)
23+
- PostgreSQL (optional, for Drizzle ORM example)
24+
25+
### Installation
26+
27+
```bash
28+
# Install dependencies
29+
bun install
30+
31+
# Run the development server
32+
bun dev
33+
```
34+
35+
Open [http://localhost:3000](http://localhost:3000) to see the example app.
36+
37+
## Pages
38+
39+
- **Home** (`/`) - Overview and getting started
40+
- **API Keys** (`/api-keys`) - Manage API keys
41+
- **Create Key** (`/api-keys/create`) - Create new API keys
42+
- **Key Details** (`/api-keys/[id]`) - View and manage individual keys
43+
- **Verify Keys** (`/verify`) - Test key verification
44+
- **Scopes** (`/scopes`) - Demonstrate scope-based permissions
45+
- **Audit Logs** (`/audit-logs`) - View audit trail
46+
- **Storage Examples** (`/storage`) - Compare different storage backends
47+
48+
## Learn More
49+
50+
- [Keypal Documentation](../README.md) - Full API documentation
51+
- [Keypal GitHub](https://github.com/izadoesdev/keypal) - Source code and issues
52+
- [Next.js Documentation](https://nextjs.org/docs) - Next.js features and API

example/app/actions.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
"use server";
2+
3+
import { memoryKeys } from "@/lib/keys";
4+
import type { PermissionScope } from "../../src/types/permissions-types";
5+
6+
export async function createApiKey(options: {
7+
ownerId: string;
8+
name?: string;
9+
scopes?: PermissionScope[];
10+
resources?: Record<string, string[]>;
11+
expiresAt?: string;
12+
}) {
13+
try {
14+
const { key, record } = await memoryKeys.create({
15+
ownerId: options.ownerId,
16+
name: options.name,
17+
scopes: options.scopes ?? ["read"],
18+
resources: options.resources,
19+
expiresAt: options.expiresAt || null,
20+
});
21+
22+
return { success: true, key, record };
23+
} catch (error) {
24+
return { success: false, error: String(error) };
25+
}
26+
}
27+
28+
export async function listApiKeys(ownerId: string) {
29+
try {
30+
const keys = await memoryKeys.list(ownerId);
31+
return { success: true, keys };
32+
} catch (error) {
33+
return { success: false, error: String(error) };
34+
}
35+
}
36+
37+
export async function verifyApiKey(key: string) {
38+
try {
39+
const result = await memoryKeys.verify(key);
40+
return result;
41+
} catch (error) {
42+
return { valid: false, error: String(error) };
43+
}
44+
}
45+
46+
export async function revokeApiKey(id: string) {
47+
try {
48+
await memoryKeys.revoke(id);
49+
return { success: true };
50+
} catch (error) {
51+
return { success: false, error: String(error) };
52+
}
53+
}
54+
55+
export async function enableApiKey(id: string) {
56+
try {
57+
await memoryKeys.enable(id);
58+
return { success: true };
59+
} catch (error) {
60+
return { success: false, error: String(error) };
61+
}
62+
}
63+
64+
export async function disableApiKey(id: string) {
65+
try {
66+
await memoryKeys.disable(id);
67+
return { success: true };
68+
} catch (error) {
69+
return { success: false, error: String(error) };
70+
}
71+
}
72+
73+
export async function getAuditLogs(keyId?: string, ownerId?: string) {
74+
try {
75+
const logs = await memoryKeys.getLogs({ keyId, ownerId, limit: 50 });
76+
return { success: true, logs };
77+
} catch (error) {
78+
return { success: false, error: String(error) };
79+
}
80+
}
81+
82+
export async function rotateApiKey(id: string) {
83+
try {
84+
const result = await memoryKeys.rotate(id);
85+
return { success: true, ...result };
86+
} catch (error) {
87+
return { success: false, error: String(error) };
88+
}
89+
}
90+
91+
export async function verifyApiKeyWithScopes(
92+
key: string,
93+
requiredScopes?: PermissionScope[],
94+
resource?: string
95+
) {
96+
try {
97+
const result = await memoryKeys.verify(key);
98+
if (!(result.valid && result.record)) {
99+
return result;
100+
}
101+
102+
// Check scopes if required
103+
if (requiredScopes && requiredScopes.length > 0) {
104+
const hasAllScopes = memoryKeys.hasAllScopes(
105+
result.record,
106+
requiredScopes,
107+
resource ? { resource } : undefined
108+
);
109+
if (!hasAllScopes) {
110+
return {
111+
valid: false,
112+
error: "Insufficient permissions",
113+
errorCode: "INSUFFICIENT_PERMISSIONS",
114+
};
115+
}
116+
}
117+
118+
return result;
119+
} catch (error) {
120+
return { valid: false, error: String(error) };
121+
}
122+
}

example/app/favicon.ico

25.3 KB
Binary file not shown.

0 commit comments

Comments
 (0)