mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-10 08:05:22 +02:00
feat: add authentication for OSS (#167)
* feat: add authentication for OSS Fixes #157 and #156 * fix: fix token generation * fix: limit fastapi workers to 1
This commit is contained in:
parent
0791975864
commit
642cc34e8c
48 changed files with 994 additions and 303 deletions
27
ui/src/app/api/auth/logout/route.ts
Normal file
27
ui/src/app/api/auth/logout/route.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { cookies } from 'next/headers';
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
const OSS_TOKEN_COOKIE = 'dograh_auth_token';
|
||||
const OSS_USER_COOKIE = 'dograh_auth_user';
|
||||
|
||||
export async function POST() {
|
||||
const cookieStore = await cookies();
|
||||
|
||||
cookieStore.set(OSS_TOKEN_COOKIE, '', {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
maxAge: 0,
|
||||
path: '/',
|
||||
});
|
||||
|
||||
cookieStore.set(OSS_USER_COOKIE, '', {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
maxAge: 0,
|
||||
path: '/',
|
||||
});
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
}
|
||||
|
|
@ -1,19 +1,18 @@
|
|||
/*
|
||||
Provides authentication token to LocalProviderWrapper once loaded
|
||||
in the browser
|
||||
in the browser.
|
||||
Returns 401 if no token cookie exists (user needs to log in).
|
||||
*/
|
||||
import { cookies } from 'next/headers';
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
const OSS_TOKEN_COOKIE = 'dograh_oss_token';
|
||||
const OSS_USER_COOKIE = 'dograh_oss_user';
|
||||
import { getAuthProvider } from '@/lib/auth/config';
|
||||
|
||||
function generateOSSToken(): string {
|
||||
return `oss_${Date.now()}_${crypto.randomUUID()}`;
|
||||
}
|
||||
const OSS_TOKEN_COOKIE = 'dograh_auth_token';
|
||||
const OSS_USER_COOKIE = 'dograh_auth_user';
|
||||
|
||||
export async function GET() {
|
||||
const authProvider = process.env.NEXT_PUBLIC_AUTH_PROVIDER || 'stack';
|
||||
const authProvider = await getAuthProvider();
|
||||
|
||||
// Only handle OSS mode
|
||||
if (authProvider !== 'local') {
|
||||
|
|
@ -21,40 +20,17 @@ export async function GET() {
|
|||
}
|
||||
|
||||
const cookieStore = await cookies();
|
||||
let token = cookieStore.get(OSS_TOKEN_COOKIE)?.value;
|
||||
let user = cookieStore.get(OSS_USER_COOKIE)?.value;
|
||||
const token = cookieStore.get(OSS_TOKEN_COOKIE)?.value;
|
||||
const user = cookieStore.get(OSS_USER_COOKIE)?.value;
|
||||
|
||||
// If no token exists, create one
|
||||
// If no token exists, return 401 (user needs to sign up or log in)
|
||||
if (!token) {
|
||||
token = generateOSSToken();
|
||||
user = JSON.stringify({
|
||||
id: token,
|
||||
name: 'Local User',
|
||||
provider: 'local',
|
||||
organizationId: `org_${token}`,
|
||||
});
|
||||
|
||||
// Set cookies
|
||||
cookieStore.set(OSS_TOKEN_COOKIE, token, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
maxAge: 60 * 60 * 24 * 30, // 30 days
|
||||
path: '/',
|
||||
});
|
||||
|
||||
cookieStore.set(OSS_USER_COOKIE, user, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
maxAge: 60 * 60 * 24 * 30, // 30 days
|
||||
path: '/',
|
||||
});
|
||||
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
||||
}
|
||||
|
||||
// Return the auth info as JSON (safe to expose to client)
|
||||
// Return the auth info as JSON
|
||||
return NextResponse.json({
|
||||
token,
|
||||
user: JSON.parse(user!),
|
||||
user: user ? JSON.parse(user) : { id: token, name: 'Local User', provider: 'local' },
|
||||
});
|
||||
}
|
||||
|
|
|
|||
33
ui/src/app/api/auth/session/route.ts
Normal file
33
ui/src/app/api/auth/session/route.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { cookies } from 'next/headers';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
const OSS_TOKEN_COOKIE = 'dograh_auth_token';
|
||||
const OSS_USER_COOKIE = 'dograh_auth_user';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const { token, user } = await request.json();
|
||||
|
||||
if (!token) {
|
||||
return NextResponse.json({ error: 'Missing token' }, { status: 400 });
|
||||
}
|
||||
|
||||
const cookieStore = await cookies();
|
||||
|
||||
cookieStore.set(OSS_TOKEN_COOKIE, token, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
maxAge: 60 * 60 * 24 * 30,
|
||||
path: '/',
|
||||
});
|
||||
|
||||
cookieStore.set(OSS_USER_COOKIE, JSON.stringify(user), {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
maxAge: 60 * 60 * 24 * 30,
|
||||
path: '/',
|
||||
});
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
}
|
||||
8
ui/src/app/api/config/auth/route.ts
Normal file
8
ui/src/app/api/config/auth/route.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { NextResponse } from 'next/server';
|
||||
|
||||
import { getAuthProvider } from '@/lib/auth/config';
|
||||
|
||||
export async function GET() {
|
||||
const provider = await getAuthProvider();
|
||||
return NextResponse.json({ provider });
|
||||
}
|
||||
|
|
@ -6,28 +6,50 @@ import type { HealthResponse } from "@/client/types.gen";
|
|||
// Import version from package.json at build time
|
||||
import packageJson from "../../../../../package.json";
|
||||
|
||||
// Internal/local URLs that are not reachable from the browser
|
||||
const INTERNAL_HOST_RE = /^https?:\/\/(localhost|127\.0\.0\.1|api)(:\d+)?(\/|$)/;
|
||||
|
||||
function isInternalUrl(url: string | undefined | null): boolean {
|
||||
return !url || INTERNAL_HOST_RE.test(url);
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
const uiVersion = packageJson.version || "dev";
|
||||
|
||||
// Fetch backend version and config from health endpoint
|
||||
let apiVersion = "unknown";
|
||||
let backendApiEndpoint: string | null = null;
|
||||
let deploymentMode = "oss";
|
||||
let authProvider = "local";
|
||||
|
||||
try {
|
||||
const response = await healthApiV1HealthGet();
|
||||
if (response.data) {
|
||||
const data = response.data as HealthResponse;
|
||||
apiVersion = data.version;
|
||||
// Pass through the backend's own endpoint for display purposes
|
||||
backendApiEndpoint = data.backend_api_endpoint;
|
||||
deploymentMode = data.deployment_mode;
|
||||
authProvider = data.auth_provider;
|
||||
}
|
||||
} catch {
|
||||
// Backend might not be reachable during build or in some deployments
|
||||
apiVersion = "unavailable";
|
||||
}
|
||||
|
||||
// For the API client base URL: prefer BACKEND_URL env, fall back to
|
||||
// health endpoint value. Skip internal/Docker-only URLs (e.g. http://api:8000)
|
||||
// that aren't reachable from the browser — the client will keep using
|
||||
// window.location.origin via the Next.js proxy instead.
|
||||
const clientCandidate = process.env.BACKEND_URL || backendApiEndpoint;
|
||||
const clientApiBaseUrl = isInternalUrl(clientCandidate) ? 'http://localhost:8000' : clientCandidate;
|
||||
|
||||
return NextResponse.json({
|
||||
ui: uiVersion,
|
||||
api: apiVersion,
|
||||
backendApiEndpoint,
|
||||
clientApiBaseUrl,
|
||||
deploymentMode,
|
||||
authProvider,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue