1.0.13
This commit is contained in:
1 parent
a88d3d5788
commit
8f3a7eb435
96 files changed
+5235
-1870
No files matched your search
@@ -6,6 +6,7 @@ import {
|
||||
type GitAuthType
|
||||
} from '$lib/server/db';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { auditGitCredential } from '$lib/server/audit';
|
||||
|
||||
export const GET: RequestHandler = async ({ cookies }) => {
|
||||
const auth = await authorize(cookies);
|
||||
@@ -33,7 +34,8 @@ export const GET: RequestHandler = async ({ cookies }) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const POST: RequestHandler = async ({ request, cookies }) => {
|
||||
export const POST: RequestHandler = async (event) => {
|
||||
const { request, cookies } = event;
|
||||
const auth = await authorize(cookies);
|
||||
if (auth.authEnabled && !await auth.can('git', 'create')) {
|
||||
return json({ error: 'Permission denied' }, { status: 403 });
|
||||
@@ -68,6 +70,9 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
|
||||
sshPassphrase: data.sshPassphrase
|
||||
});
|
||||
|
||||
// Audit log
|
||||
await auditGitCredential(event, 'create', credential.id, credential.name);
|
||||
|
||||
return json({
|
||||
id: credential.id,
|
||||
name: credential.name,
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
type GitAuthType
|
||||
} from '$lib/server/db';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { auditGitCredential } from '$lib/server/audit';
|
||||
import { computeAuditDiff } from '$lib/utils/diff';
|
||||
|
||||
export const GET: RequestHandler = async ({ params, cookies }) => {
|
||||
const auth = await authorize(cookies);
|
||||
@@ -42,7 +44,8 @@ export const GET: RequestHandler = async ({ params, cookies }) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const PUT: RequestHandler = async ({ params, request, cookies }) => {
|
||||
export const PUT: RequestHandler = async (event) => {
|
||||
const { params, request, cookies } = event;
|
||||
const auth = await authorize(cookies);
|
||||
if (auth.authEnabled && !await auth.can('git', 'edit')) {
|
||||
return json({ error: 'Permission denied' }, { status: 403 });
|
||||
@@ -78,6 +81,15 @@ export const PUT: RequestHandler = async ({ params, request, cookies }) => {
|
||||
return json({ error: 'Failed to update credential' }, { status: 500 });
|
||||
}
|
||||
|
||||
// Compute diff for audit (only non-sensitive fields)
|
||||
const diff = computeAuditDiff(
|
||||
{ name: existing.name, authType: existing.authType, username: existing.username },
|
||||
{ name: credential.name, authType: credential.authType, username: credential.username }
|
||||
);
|
||||
|
||||
// Audit log
|
||||
await auditGitCredential(event, 'update', credential.id, credential.name, diff);
|
||||
|
||||
return json({
|
||||
id: credential.id,
|
||||
name: credential.name,
|
||||
@@ -97,7 +109,8 @@ export const PUT: RequestHandler = async ({ params, request, cookies }) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const DELETE: RequestHandler = async ({ params, cookies }) => {
|
||||
export const DELETE: RequestHandler = async (event) => {
|
||||
const { params, cookies } = event;
|
||||
const auth = await authorize(cookies);
|
||||
if (auth.authEnabled && !await auth.can('git', 'delete')) {
|
||||
return json({ error: 'Permission denied' }, { status: 403 });
|
||||
@@ -109,11 +122,20 @@ export const DELETE: RequestHandler = async ({ params, cookies }) => {
|
||||
return json({ error: 'Invalid credential ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
const deleted = await deleteGitCredential(id);
|
||||
if (!deleted) {
|
||||
// Get credential name before deletion for audit log
|
||||
const credential = await getGitCredential(id);
|
||||
if (!credential) {
|
||||
return json({ error: 'Credential not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
const deleted = await deleteGitCredential(id);
|
||||
if (!deleted) {
|
||||
return json({ error: 'Failed to delete credential' }, { status: 500 });
|
||||
}
|
||||
|
||||
// Audit log
|
||||
await auditGitCredential(event, 'delete', id, credential.name);
|
||||
|
||||
return json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Failed to delete git credential:', error);
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getGitRepository, getGitCredential } from '$lib/server/db';
|
||||
import { previewRepoEnvFiles } from '$lib/server/git';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
|
||||
/**
|
||||
* POST /api/git/preview-env
|
||||
* Clone a git repository to a temp directory and read env files for preview.
|
||||
* Used when creating a new git stack to populate the env editor.
|
||||
*
|
||||
* Body: {
|
||||
* repositoryId?: number, // Existing repository
|
||||
* url?: string, // OR new repo URL
|
||||
* branch?: string, // Branch (default: main)
|
||||
* credentialId?: number, // Credential for auth
|
||||
* composePath: string, // Path to compose file
|
||||
* envFilePath?: string // Optional additional env file
|
||||
* }
|
||||
*
|
||||
* Returns: {
|
||||
* vars: Record<string, string>, // Merged env variables
|
||||
* sources: { // Which file each var came from
|
||||
* [key: string]: '.env' | 'envFile'
|
||||
* },
|
||||
* error?: string
|
||||
* }
|
||||
*/
|
||||
export const POST: RequestHandler = async ({ request, cookies }) => {
|
||||
const auth = await authorize(cookies);
|
||||
|
||||
// Basic permission check - must be able to create stacks
|
||||
if (auth.authEnabled && !auth.isAuthenticated) {
|
||||
return json({ error: 'Authentication required' }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await request.json();
|
||||
|
||||
if (!data.composePath || typeof data.composePath !== 'string') {
|
||||
return json({ error: 'Compose path is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
let repoUrl: string;
|
||||
let branch: string = 'main';
|
||||
let credentialId: number | null = null;
|
||||
|
||||
if (data.repositoryId) {
|
||||
// Use existing repository
|
||||
const repo = await getGitRepository(data.repositoryId);
|
||||
if (!repo) {
|
||||
return json({ error: 'Repository not found' }, { status: 404 });
|
||||
}
|
||||
repoUrl = repo.url;
|
||||
branch = repo.branch;
|
||||
credentialId = repo.credentialId;
|
||||
} else if (data.url) {
|
||||
// New repository details
|
||||
repoUrl = data.url;
|
||||
branch = data.branch || 'main';
|
||||
credentialId = data.credentialId || null;
|
||||
} else {
|
||||
return json({ error: 'Either repositoryId or url is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Get credential if specified
|
||||
let credential = null;
|
||||
if (credentialId) {
|
||||
credential = await getGitCredential(credentialId);
|
||||
}
|
||||
|
||||
const result = await previewRepoEnvFiles({
|
||||
repoUrl,
|
||||
branch,
|
||||
credential,
|
||||
composePath: data.composePath,
|
||||
envFilePath: data.envFilePath || null
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
return json({ vars: {}, sources: {}, error: result.error }, { status: 400 });
|
||||
}
|
||||
|
||||
return json({
|
||||
vars: result.vars,
|
||||
sources: result.sources
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error('Failed to preview env files:', error);
|
||||
return json({ error: error.message || 'Failed to preview env files' }, { status: 500 });
|
||||
}
|
||||
};
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
getGitCredentials
|
||||
} from '$lib/server/db';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { auditGitRepository } from '$lib/server/audit';
|
||||
|
||||
export const GET: RequestHandler = async ({ url, cookies }) => {
|
||||
const auth = await authorize(cookies);
|
||||
@@ -24,7 +25,8 @@ export const GET: RequestHandler = async ({ url, cookies }) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const POST: RequestHandler = async ({ request, cookies }) => {
|
||||
export const POST: RequestHandler = async (event) => {
|
||||
const { request, cookies } = event;
|
||||
const auth = await authorize(cookies);
|
||||
if (auth.authEnabled && !await auth.can('git', 'create')) {
|
||||
return json({ error: 'Permission denied' }, { status: 403 });
|
||||
@@ -59,6 +61,9 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
|
||||
credentialId: data.credentialId || null
|
||||
});
|
||||
|
||||
// Audit log
|
||||
await auditGitRepository(event, 'create', repository.id, repository.name);
|
||||
|
||||
return json(repository);
|
||||
} catch (error: any) {
|
||||
console.error('Failed to create git repository:', error);
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
} from '$lib/server/db';
|
||||
import { deleteRepositoryFiles } from '$lib/server/git';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { auditGitRepository } from '$lib/server/audit';
|
||||
import { computeAuditDiff } from '$lib/utils/diff';
|
||||
|
||||
export const GET: RequestHandler = async ({ params, cookies }) => {
|
||||
const auth = await authorize(cookies);
|
||||
@@ -33,7 +35,8 @@ export const GET: RequestHandler = async ({ params, cookies }) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const PUT: RequestHandler = async ({ params, request, cookies }) => {
|
||||
export const PUT: RequestHandler = async (event) => {
|
||||
const { params, request, cookies } = event;
|
||||
const auth = await authorize(cookies);
|
||||
if (auth.authEnabled && !await auth.can('git', 'edit')) {
|
||||
return json({ error: 'Permission denied' }, { status: 403 });
|
||||
@@ -74,6 +77,12 @@ export const PUT: RequestHandler = async ({ params, request, cookies }) => {
|
||||
return json({ error: 'Failed to update repository' }, { status: 500 });
|
||||
}
|
||||
|
||||
// Compute diff for audit
|
||||
const diff = computeAuditDiff(existing, repository);
|
||||
|
||||
// Audit log
|
||||
await auditGitRepository(event, 'update', repository.id, repository.name, diff);
|
||||
|
||||
return json(repository);
|
||||
} catch (error: any) {
|
||||
console.error('Failed to update git repository:', error);
|
||||
@@ -84,7 +93,8 @@ export const PUT: RequestHandler = async ({ params, request, cookies }) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const DELETE: RequestHandler = async ({ params, cookies }) => {
|
||||
export const DELETE: RequestHandler = async (event) => {
|
||||
const { params, cookies } = event;
|
||||
const auth = await authorize(cookies);
|
||||
if (auth.authEnabled && !await auth.can('git', 'delete')) {
|
||||
return json({ error: 'Permission denied' }, { status: 403 });
|
||||
@@ -96,14 +106,23 @@ export const DELETE: RequestHandler = async ({ params, cookies }) => {
|
||||
return json({ error: 'Invalid repository ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Get repository name before deletion for audit log
|
||||
const repository = await getGitRepository(id);
|
||||
if (!repository) {
|
||||
return json({ error: 'Repository not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Delete repository files first
|
||||
deleteRepositoryFiles(id);
|
||||
|
||||
const deleted = await deleteGitRepository(id);
|
||||
if (!deleted) {
|
||||
return json({ error: 'Repository not found' }, { status: 404 });
|
||||
return json({ error: 'Failed to delete repository' }, { status: 500 });
|
||||
}
|
||||
|
||||
// Audit log
|
||||
await auditGitRepository(event, 'delete', id, repository.name);
|
||||
|
||||
return json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Failed to delete git repository:', error);
|
||||
|
||||
@@ -6,12 +6,14 @@ import {
|
||||
getGitCredentials,
|
||||
getGitRepository,
|
||||
createGitRepository,
|
||||
upsertStackSource
|
||||
upsertStackSource,
|
||||
setStackEnvVars
|
||||
} from '$lib/server/db';
|
||||
import { deployGitStack } from '$lib/server/git';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { registerSchedule } from '$lib/server/scheduler';
|
||||
import { secureRandomBytes } from '$lib/server/crypto-fallback';
|
||||
import { auditGitStack } from '$lib/server/audit';
|
||||
|
||||
// Stack name validation: must start with alphanumeric, can contain alphanumeric, hyphens, underscores
|
||||
const STACK_NAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
|
||||
@@ -37,7 +39,8 @@ export const GET: RequestHandler = async ({ url, cookies }) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const POST: RequestHandler = async ({ request, cookies }) => {
|
||||
export const POST: RequestHandler = async (event) => {
|
||||
const { request, cookies } = event;
|
||||
const auth = await authorize(cookies);
|
||||
|
||||
try {
|
||||
@@ -132,9 +135,26 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
|
||||
await registerSchedule(gitStack.id, 'git_stack_sync', gitStack.environmentId);
|
||||
}
|
||||
|
||||
// Audit log
|
||||
await auditGitStack(event, 'create', gitStack.id, gitStack.stackName, gitStack.environmentId);
|
||||
|
||||
// Save environment variable overrides before deploying
|
||||
if (data.envVars && Array.isArray(data.envVars) && data.envVars.length > 0) {
|
||||
await setStackEnvVars(
|
||||
trimmedStackName,
|
||||
data.environmentId || null,
|
||||
data.envVars.filter((v: any) => v.key?.trim()).map((v: any) => ({
|
||||
key: v.key.trim(),
|
||||
value: v.value ?? '',
|
||||
isSecret: v.isSecret ?? false
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
// If deployNow is set, deploy immediately
|
||||
if (data.deployNow) {
|
||||
const deployResult = await deployGitStack(gitStack.id);
|
||||
await auditGitStack(event, 'deploy', gitStack.id, gitStack.stackName, gitStack.environmentId);
|
||||
return json({
|
||||
...gitStack,
|
||||
deployResult: deployResult
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getGitStack, updateGitStack, deleteGitStack, deleteStackSource, updateStackSourceName, updateStackEnvVarsName } from '$lib/server/db';
|
||||
import { getGitStack, updateGitStack, deleteGitStack, deleteStackSource, updateStackSourceName, updateStackEnvVarsName, setStackEnvVars } from '$lib/server/db';
|
||||
import { deleteGitStackFiles, deployGitStack } from '$lib/server/git';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { registerSchedule, unregisterSchedule } from '$lib/server/scheduler';
|
||||
import { auditGitStack } from '$lib/server/audit';
|
||||
import { computeAuditDiff } from '$lib/utils/diff';
|
||||
|
||||
// Stack name validation: must start with alphanumeric, can contain alphanumeric, hyphens, underscores
|
||||
const STACK_NAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
|
||||
@@ -30,7 +32,8 @@ export const GET: RequestHandler = async ({ params, cookies }) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const PUT: RequestHandler = async ({ params, request, cookies }) => {
|
||||
export const PUT: RequestHandler = async (event) => {
|
||||
const { params, request, cookies } = event;
|
||||
const auth = await authorize(cookies);
|
||||
|
||||
try {
|
||||
@@ -84,9 +87,33 @@ export const PUT: RequestHandler = async ({ params, request, cookies }) => {
|
||||
unregisterSchedule(id, 'git_stack_sync');
|
||||
}
|
||||
|
||||
// Compute diff for audit (exclude sensitive fields)
|
||||
const diff = computeAuditDiff(existing, updated, {
|
||||
excludeFields: ['webhookSecret', 'createdAt', 'updatedAt', 'lastSync', 'lastCommit', 'syncStatus', 'syncError']
|
||||
});
|
||||
|
||||
// Audit log
|
||||
await auditGitStack(event, 'update', updated.id, updated.stackName, updated.environmentId, diff);
|
||||
|
||||
// Save environment variable overrides before deploying
|
||||
if (data.envVars && Array.isArray(data.envVars)) {
|
||||
const stackName = data.stackName || existing.stackName;
|
||||
const envId = updated.environmentId ?? null;
|
||||
await setStackEnvVars(
|
||||
stackName,
|
||||
envId,
|
||||
data.envVars.filter((v: any) => v.key?.trim()).map((v: any) => ({
|
||||
key: v.key.trim(),
|
||||
value: v.value ?? '',
|
||||
isSecret: v.isSecret ?? false
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
// If deployNow is set, deploy after saving
|
||||
if (data.deployNow) {
|
||||
const deployResult = await deployGitStack(id);
|
||||
await auditGitStack(event, 'deploy', updated.id, updated.stackName, updated.environmentId);
|
||||
return json({
|
||||
...updated,
|
||||
deployResult
|
||||
@@ -103,7 +130,8 @@ export const PUT: RequestHandler = async ({ params, request, cookies }) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const DELETE: RequestHandler = async ({ params, cookies }) => {
|
||||
export const DELETE: RequestHandler = async (event) => {
|
||||
const { params, cookies } = event;
|
||||
const auth = await authorize(cookies);
|
||||
|
||||
try {
|
||||
@@ -122,7 +150,7 @@ export const DELETE: RequestHandler = async ({ params, cookies }) => {
|
||||
unregisterSchedule(id, 'git_stack_sync');
|
||||
|
||||
// Delete git files first
|
||||
deleteGitStackFiles(id);
|
||||
await deleteGitStackFiles(id, existing.stackName, existing.environmentId);
|
||||
|
||||
// Delete the stack_sources record to free up the stack name
|
||||
await deleteStackSource(existing.stackName, existing.environmentId);
|
||||
@@ -130,6 +158,9 @@ export const DELETE: RequestHandler = async ({ params, cookies }) => {
|
||||
// Delete from database
|
||||
await deleteGitStack(id);
|
||||
|
||||
// Audit log
|
||||
await auditGitStack(event, 'delete', id, existing.stackName, existing.environmentId);
|
||||
|
||||
return json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Failed to delete git stack:', error);
|
||||
|
||||
@@ -3,8 +3,10 @@ import type { RequestHandler } from './$types';
|
||||
import { getGitStack } from '$lib/server/db';
|
||||
import { deployGitStack } from '$lib/server/git';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { auditGitStack } from '$lib/server/audit';
|
||||
|
||||
export const POST: RequestHandler = async ({ params, cookies }) => {
|
||||
export const POST: RequestHandler = async (event) => {
|
||||
const { params, cookies } = event;
|
||||
const auth = await authorize(cookies);
|
||||
|
||||
try {
|
||||
@@ -20,6 +22,10 @@ export const POST: RequestHandler = async ({ params, cookies }) => {
|
||||
}
|
||||
|
||||
const result = await deployGitStack(id);
|
||||
|
||||
// Audit log
|
||||
await auditGitStack(event, 'deploy', id, gitStack.stackName, gitStack.environmentId);
|
||||
|
||||
return json(result);
|
||||
} catch (error) {
|
||||
console.error('Failed to deploy git stack:', error);
|
||||
|
||||
Reference in new issue
Block a user