Skip to content

Commit ead1d9e

Browse files
authored
chore: make envs optional (#10)
for standalone usage with akashml api key
1 parent 8d279b5 commit ead1d9e

29 files changed

Lines changed: 1125 additions & 880 deletions

File tree

README.md

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,25 @@ AkashChat leverages the decentralized cloud infrastructure of Akash Network, off
5050

5151
## Getting Started
5252

53+
### Minimal Setup
54+
55+
AkashChat can run with just an API key! The minimal configuration requires:
56+
57+
- Node.js (v18 or higher)
58+
- `API_KEY` environment variable
59+
60+
Redis, Auth0, Database, and other features are **optional** and only needed for:
61+
- **Redis**: Session management and caching (without it, uses simple cookie-based sessions)
62+
- **Auth0 + Database**: Multi-user accounts with persistent chat history
63+
- **Access Token**: Frontend access control for private instances
64+
5365
### Prerequisites
5466

67+
For the full-featured setup:
68+
5569
- Node.js (v18 or higher)
56-
- Redis server (for caching and session management)
70+
- Redis server (optional, for session management and caching)
71+
- PostgreSQL database (optional, for multi-user support with Auth0)
5772
- API keys/endpoints for the AI models you want to use
5873

5974
### Installation
@@ -69,9 +84,17 @@ AkashChat leverages the decentralized cloud infrastructure of Akash Network, off
6984
npm install
7085
```
7186

72-
3. Create a `.env.local` file in the root directory with the following variables:
87+
3. Create a `.env.local` file in the root directory.
88+
89+
**Minimal configuration** (only required variables):
90+
```env
91+
# API Configuration - REQUIRED
92+
API_KEY=your_api_key_here
93+
```
94+
95+
**Full configuration** (all available options):
7396
```env
74-
# API Configuration
97+
# API Configuration - REQUIRED
7598
API_KEY=your_api_key_here
7699
API_ENDPOINT=your_api_endpoint_here
77100
DEFAULT_MODEL=your_default_model_here
@@ -118,23 +141,23 @@ AkashChat leverages the decentralized cloud infrastructure of Akash Network, off
118141

119142
| Variable | Description | Required | Default |
120143
|----------|-------------|----------|---------|
121-
| API_KEY | Authentication key for Akash AI API access | Yes | - |
122-
| API_ENDPOINT | Base URL for Akash AI API | Yes | https://chatapi.akash.network/api/v1 |
144+
| **API_KEY** | **Authentication key for Akash AI API access** | **Yes** | **-** |
145+
| API_ENDPOINT | Base URL for Akash AI API | No | https://api.akashml.com/v1 |
123146
| DEFAULT_MODEL | Default AI model to use | No | Qwen-QwQ-32B |
124-
| REDIS_URL | Connection URL for Redis | Yes | redis://localhost:6379 |
125-
| CACHE_TTL | Cache time-to-live in seconds | No | 600 (10 minutes) |
126-
| ACCESS_TOKEN | Token for API and frontend protection | No | - |
147+
| REDIS_URL | Connection URL for Redis (for session management) | No | - |
148+
| CACHE_TTL | Cache/session time-to-live in seconds | No | 600 (10 minutes) |
149+
| ACCESS_TOKEN | Token for API and frontend protection (SHA-256 hashed automatically) | No | - |
127150
| WS_TRANSCRIPTION_URLS | Comma-separated WebSocket URLs for voice transcription | No | - |
128151
| WS_TRANSCRIPTION_MODEL | Model for voice transcription | No | mobiuslabsgmbh/faster-whisper-large-v3-turbo |
129152
| IMG_API_KEY | Authentication key for AkashGen image generation | No | - |
130153
| IMG_ENDPOINT | Endpoint for AkashGen image generation | No | - |
131154
| IMG_GEN_FN_MODEL | Model for AkashGen image generation | No | - |
132-
| AUTH0_SECRET | Auth0 session secret for user authentication | No | - |
155+
| AUTH0_SECRET | Auth0 session secret (required for multi-user mode) | No | - |
133156
| AUTH0_BASE_URL | Base URL for Auth0 configuration | No | - |
134157
| AUTH0_ISSUER_BASE_URL | Auth0 issuer base URL | No | - |
135158
| AUTH0_CLIENT_ID | Auth0 application client ID | No | - |
136159
| AUTH0_CLIENT_SECRET | Auth0 application client secret | No | - |
137-
| DATABASE_URL | PostgreSQL connection URL for data persistence | Yes | - |
160+
| DATABASE_URL | PostgreSQL connection URL (required for multi-user mode) | No | - |
138161
| LITELLM_BASE_URL | LiteLLM proxy base URL | No | - |
139162
| LITELLM_API_KEY | LiteLLM proxy API key | No | - |
140163

app/api/auth/[...auth0]/route.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { isAuth0Configured, isDevBypassEnabled } from '@/lib/auth';
12
import { handleAuth } from '@auth0/nextjs-auth0';
23
import { NextRequest, NextResponse } from 'next/server';
34

@@ -26,10 +27,6 @@ function getDevUser() {
2627
};
2728
}
2829

29-
function isDevBypassEnabled() {
30-
return process.env.NODE_ENV === 'development' && process.env.DEV_BYPASS_AUTH === 'true';
31-
}
32-
3330
function handleDevLogin(request: NextRequest): NextResponse {
3431
const returnTo = request.nextUrl.searchParams.get('returnTo') || '/';
3532
const redirectUrl = new URL(returnTo, request.url);
@@ -77,16 +74,36 @@ export async function GET(request: NextRequest, context: { params: Promise<{ aut
7774
const params = await context.params;
7875
const route = params.auth0?.[0];
7976

80-
if (isDevBypassEnabled()) {
77+
// Handle dev bypass or when Auth0 is not configured
78+
if (isDevBypassEnabled() || !isAuth0Configured()) {
8179
switch (route) {
8280
case 'login':
81+
if (!isAuth0Configured()) {
82+
return NextResponse.json({ error: 'Authentication not configured' }, { status: 501 });
83+
}
8384
return handleDevLogin(request);
8485
case 'logout':
86+
if (!isAuth0Configured()) {
87+
return NextResponse.redirect(new URL('/', request.url));
88+
}
8589
return handleDevLogout(request);
8690
case 'me':
91+
if (!isAuth0Configured()) {
92+
return NextResponse.json(null, { status: 401 });
93+
}
8794
return handleDevMe(request);
8895
case 'callback':
8996
return NextResponse.redirect(new URL('/', request.url));
97+
case 'session':
98+
case 'status':
99+
if (!isAuth0Configured()) {
100+
return NextResponse.json({ isAuthenticated: false });
101+
}
102+
break;
103+
default:
104+
if (!isAuth0Configured()) {
105+
return NextResponse.json({ error: 'Authentication not configured' }, { status: 501 });
106+
}
90107
}
91108
}
92109

app/api/auth/session/refresh/route.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,21 @@ import crypto from 'crypto';
33
import { NextResponse } from 'next/server';
44

55
import { CACHE_TTL } from '@/app/config/api';
6-
import { checkApiAccessToken } from '@/lib/auth';
7-
import { validateSession, storeSession } from '@/lib/redis';
8-
import redis from '@/lib/redis';
6+
import redis, { validateSession, storeSession, isRedisAvailable } from '@/lib/redis';
97

108
const RATE_LIMIT_WINDOW = Math.floor(CACHE_TTL * 0.20 * 0.25);
119
const MAX_REQUESTS = 3;
1210

1311
async function isRateLimited(token: string): Promise<boolean> {
12+
if (!redis) {return false;}
13+
1414
const key = `ratelimit:refresh:${token}`;
1515
const now = Math.floor(Date.now() / 1000);
1616

1717
try {
1818
await redis.zadd(key, now, now.toString());
19-
2019
await redis.zremrangebyscore(key, 0, now - RATE_LIMIT_WINDOW);
21-
2220
const requestCount = await redis.zcard(key);
23-
2421
await redis.expire(key, RATE_LIMIT_WINDOW);
2522

2623
return requestCount > MAX_REQUESTS;
@@ -53,9 +50,9 @@ export async function POST(request: Request) {
5350
return NextResponse.json({ error: 'No session token found' }, { status: 401 });
5451
}
5552

56-
const authCheckResponse = checkApiAccessToken(request);
57-
if (authCheckResponse) {
58-
return authCheckResponse;
53+
// Skip session management if Redis is not available
54+
if (!isRedisAvailable()) {
55+
return NextResponse.json({ success: true });
5956
}
6057

6158
if (await isRateLimited(currentToken)) {
@@ -64,7 +61,7 @@ export async function POST(request: Request) {
6461
{ status: 429 }
6562
);
6663
}
67-
const ttl = await redis.ttl(`session:${currentToken}`);
64+
const ttl = await redis!.ttl(`session:${currentToken}`);
6865
const isValid = await validateSession(currentToken);
6966

7067
if (isValid && ttl > CACHE_TTL * 0.20) {

app/api/auth/session/route.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { NextResponse } from 'next/server';
44

55
import { CACHE_TTL } from '@/app/config/api';
66
import { checkApiAccessToken } from '@/lib/auth';
7-
import { storeSession } from '@/lib/redis';
7+
import { storeSession, validateSession, isRedisAvailable } from '@/lib/redis';
88

99
export async function GET(request: Request) {
1010
if (process.env.NODE_ENV === 'production') {
@@ -27,12 +27,56 @@ export async function GET(request: Request) {
2727
return NextResponse.json({ error: 'Invalid request - invalid fetch mode' }, { status: 403 });
2828
}
2929
}
30-
30+
31+
// If Redis is not available, use a simple cookie flag instead
32+
if (!isRedisAvailable()) {
33+
const cookieHeader = request.headers.get('cookie');
34+
const hasAuthCookie = cookieHeader?.includes('session_token=validated');
35+
36+
// If already validated (has cookie), allow through
37+
if (hasAuthCookie) {
38+
return NextResponse.json({ success: true });
39+
}
40+
41+
// No cookie, require access token
42+
const authCheckResponse = checkApiAccessToken(request);
43+
if (authCheckResponse) {
44+
return authCheckResponse;
45+
}
46+
47+
// Access token valid, set persistent cookie
48+
const response = NextResponse.json({ success: true });
49+
response.cookies.set('session_token', 'validated', {
50+
httpOnly: process.env.NODE_ENV === 'production',
51+
secure: process.env.NODE_ENV === 'production',
52+
sameSite: 'strict',
53+
maxAge: 24 * 60 * 60,
54+
path: '/',
55+
partitioned: process.env.NODE_ENV === 'production',
56+
});
57+
return response;
58+
}
59+
60+
// Check if there's already a valid session cookie
61+
const cookieHeader = request.headers.get('cookie');
62+
const existingToken = cookieHeader?.split(';')
63+
.find(c => c.trim().startsWith('session_token='))
64+
?.split('=')[1];
65+
66+
if (existingToken) {
67+
const isValid = await validateSession(existingToken);
68+
if (isValid) {
69+
return NextResponse.json({ success: true });
70+
}
71+
}
72+
73+
// No valid session, require access token
3174
const authCheckResponse = checkApiAccessToken(request);
3275
if (authCheckResponse) {
3376
return authCheckResponse;
3477
}
3578

79+
// Create new session
3680
const sessionToken = crypto.randomBytes(32).toString('hex');
3781

3882
await storeSession(sessionToken);

app/api/auth/status/route.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import { NextResponse } from 'next/server';
22

33
import { ACCESS_TOKEN } from '@/app/config/api';
4+
import { isAuth0Configured } from '@/lib/auth';
45

56
/**
6-
* API endpoint to check if an access token is required for the application
7-
* This allows the client to proactively check if token authentication is needed
7+
* API endpoint to check auth configuration status
88
*/
99
export async function GET() {
1010
return NextResponse.json({
1111
requiresAccessToken: !!ACCESS_TOKEN,
12-
message: ACCESS_TOKEN
13-
? 'This application requires an access token to continue'
12+
authEnabled: isAuth0Configured(),
13+
message: ACCESS_TOKEN
14+
? 'This application requires an access token to continue'
1415
: 'No access token required for this application',
1516
});
1617
}

app/api/chat/route.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import { createOpenAI } from '@ai-sdk/openai';
2-
import { getSession } from '@auth0/nextjs-auth0';
32
import { streamText, createDataStreamResponse, generateText, simulateReadableStream, Message } from 'ai';
4-
import { NextRequest, NextResponse } from 'next/server';
3+
import { NextRequest } from 'next/server';
54
import cl100k_base from "tiktoken/encoders/cl100k_base.json";
65
import { Tiktoken } from "tiktoken/lite";
76

87
import { apiEndpoint, apiKey, imgGenFnModel, DEFAULT_SYSTEM_PROMPT } from '@/app/config/api';
98
import { defaultModel } from '@/app/config/models';
10-
import { withSessionAuth } from '@/lib/auth';
9+
import { withSessionAuth, getOptionalSession } from '@/lib/auth';
1110
import { getAvailableModelsForUser } from '@/lib/models';
1211
import { checkTokenLimit, incrementTokenUsageWithMultiplier, getClientIP, getRateLimitConfigForUser, storeConversationTokens } from '@/lib/rate-limit';
1312
import { LiteLLMService } from '@/lib/services/litellm-service';
@@ -103,7 +102,7 @@ function createOpenAIWithRateLimit(apiKey: string) {
103102
const openai = createOpenAIWithRateLimit(apiKey);
104103

105104
async function handlePostRequest(req: NextRequest) {
106-
const session = await getSession(req, NextResponse.next());
105+
const session = await getOptionalSession(req);
107106
const isAuthenticated = !!session?.user;
108107

109108
let userApiKey: string | null = null;

app/api/chats/load/route.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
import { getSession } from '@auth0/nextjs-auth0';
21
import { NextRequest, NextResponse } from 'next/server';
32

3+
import { getOptionalSession, isAuth0Configured } from '@/lib/auth';
44
import { createDatabaseService } from '@/lib/services/database-service';
55
import { createEncryptionService } from '@/lib/services/encryption-service';
66

77
export async function GET(request: NextRequest) {
88
try {
9-
const session = await getSession(request, new NextResponse());
9+
if (!isAuth0Configured()) {
10+
return NextResponse.json({ chatSessions: [], count: 0 });
11+
}
12+
13+
const session = await getOptionalSession(request);
1014
if (!session?.user?.sub) {
1115
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
1216
}

0 commit comments

Comments
 (0)