Browse Source

bug-fixes for api

main
Silberengel 4 weeks ago
parent
commit
dcb62688be
  1. 3
      src/lib/components/NavBar.svelte
  2. 6
      src/lib/services/git/repo-manager.ts
  3. 3
      src/lib/services/messaging/event-forwarder.ts
  4. 3
      src/lib/services/messaging/preferences-storage.server.ts
  5. 3
      src/lib/services/nostr/repo-polling.ts
  6. 3
      src/lib/services/nostr/user-level-service.ts
  7. 56
      src/lib/utils/api-repo-helper.ts
  8. 3
      src/routes/+page.svelte
  9. 50
      src/routes/api/repos/[npub]/[repo]/branches/+server.ts
  10. 3
      src/routes/api/repos/[npub]/[repo]/clone/+server.ts
  11. 43
      src/routes/api/repos/[npub]/[repo]/commits/+server.ts
  12. 36
      src/routes/api/repos/[npub]/[repo]/download/+server.ts
  13. 30
      src/routes/api/repos/[npub]/[repo]/file/+server.ts
  14. 3
      src/routes/api/repos/[npub]/[repo]/fork/+server.ts
  15. 30
      src/routes/api/repos/[npub]/[repo]/readme/+server.ts
  16. 47
      src/routes/api/repos/[npub]/[repo]/tree/+server.ts
  17. 3
      src/routes/api/user/git-dashboard/+server.ts
  18. 7
      src/routes/api/user/messaging-preferences/+server.ts
  19. 3
      src/routes/api/user/messaging-preferences/summary/+server.ts
  20. 3
      src/routes/api/user/ssh-keys/+server.ts
  21. 4
      src/routes/signup/+page.svelte

3
src/lib/components/NavBar.svelte

@ -119,7 +119,8 @@ @@ -119,7 +119,8 @@
updateActivity();
// Show success message
if (levelResult.level === 'unlimited') {
const { hasUnlimitedAccess } = await import('../../lib/utils/user-access.js');
if (hasUnlimitedAccess(levelResult.level)) {
console.log('Unlimited access granted!');
} else if (levelResult.level === 'rate_limited') {
console.log('Logged in with rate-limited access.');

6
src/lib/services/git/repo-manager.ts

@ -124,8 +124,9 @@ export class RepoManager { @@ -124,8 +124,9 @@ export class RepoManager {
const isNewRepo = !repoExists;
if (isNewRepo && !isExistingRepo) {
const { getCachedUserLevel } = await import('../security/user-level-cache.js');
const { hasUnlimitedAccess } = await import('../utils/user-access.js');
const userLevel = getCachedUserLevel(event.pubkey);
if (!userLevel || userLevel.level !== 'unlimited') {
if (!hasUnlimitedAccess(userLevel?.level)) {
throw new Error(`Repository creation requires unlimited access. User has level: ${userLevel?.level || 'none'}`);
}
}
@ -532,8 +533,9 @@ export class RepoManager { @@ -532,8 +533,9 @@ export class RepoManager {
// For private repos, require owner to have unlimited access to prevent unauthorized creation
if (!isPublic) {
const { getCachedUserLevel } = await import('../security/user-level-cache.js');
const { hasUnlimitedAccess } = await import('../utils/user-access.js');
const userLevel = getCachedUserLevel(announcementEvent.pubkey);
if (!userLevel || userLevel.level !== 'unlimited') {
if (!hasUnlimitedAccess(userLevel?.level)) {
logger.warn({
npub,
repoName,

3
src/lib/services/messaging/event-forwarder.ts

@ -621,7 +621,8 @@ export async function forwardEventIfEnabled( @@ -621,7 +621,8 @@ export async function forwardEventIfEnabled(
try {
// Early returns for eligibility checks
const cached = getCachedUserLevel(userPubkeyHex);
if (!cached || cached.level !== 'unlimited') {
const { hasUnlimitedAccess } = await import('../utils/user-access.js');
if (!hasUnlimitedAccess(cached?.level)) {
return;
}

3
src/lib/services/messaging/preferences-storage.server.ts

@ -213,7 +213,8 @@ export async function storePreferences( @@ -213,7 +213,8 @@ export async function storePreferences(
// Verify user has unlimited access
const cached = getCachedUserLevel(userPubkeyHex);
if (!cached || cached.level !== 'unlimited') {
const { hasUnlimitedAccess } = await import('../utils/user-access.js');
if (!hasUnlimitedAccess(cached?.level)) {
throw new Error('Messaging forwarding requires unlimited access');
}

3
src/lib/services/nostr/repo-polling.ts

@ -167,7 +167,8 @@ export class RepoPollingService { @@ -167,7 +167,8 @@ export class RepoPollingService {
// This prevents spam and abuse
if (!isExistingRepo) {
const userLevel = getCachedUserLevel(event.pubkey);
if (!userLevel || userLevel.level !== 'unlimited') {
const { hasUnlimitedAccess } = await import('../utils/user-access.js');
if (!hasUnlimitedAccess(userLevel?.level)) {
logger.warn({
eventId: event.id,
pubkey: event.pubkey.slice(0, 16) + '...',

3
src/lib/services/nostr/user-level-service.ts

@ -16,6 +16,7 @@ import { createProofEvent } from './relay-write-proof.js'; @@ -16,6 +16,7 @@ import { createProofEvent } from './relay-write-proof.js';
import { nip19 } from 'nostr-tools';
import { NostrClient } from './nostr-client.js';
import { DEFAULT_NOSTR_RELAYS } from '../../config.js';
import { hasUnlimitedAccess } from '../../utils/user-access.js';
export type UserLevel = 'unlimited' | 'rate_limited' | 'strictly_rate_limited';
@ -84,7 +85,7 @@ export async function checkRelayWriteAccess( @@ -84,7 +85,7 @@ export async function checkRelayWriteAccess(
const result = await response.json();
return {
hasAccess: result.level === 'unlimited',
hasAccess: hasUnlimitedAccess(result.level as UserLevel),
error: result.error
};
} catch (error) {

56
src/lib/utils/api-repo-helper.ts

@ -0,0 +1,56 @@ @@ -0,0 +1,56 @@
/**
* Helper utilities for API-based repository fetching
* Used by endpoints to fetch repo metadata without cloning
*/
import { fetchRepoMetadata, extractGitUrls } from '../services/git/api-repo-fetcher.js';
import type { NostrEvent } from '../types/nostr.js';
import logger from '../services/logger.js';
/**
* Try to fetch repository metadata via API from clone URLs
* Returns null if API fetching fails or no clone URLs available
*/
export async function tryApiFetch(
announcementEvent: NostrEvent,
npub: string,
repoName: string
): Promise<{
branches: Array<{ name: string; commit: { sha: string; message: string; author: string; date: string } }>;
defaultBranch: string;
files?: Array<{ name: string; path: string; type: 'file' | 'dir'; size?: number }>;
commits?: Array<{ sha: string; message: string; author: string; date: string }>;
} | null> {
try {
const cloneUrls = extractGitUrls(announcementEvent);
if (cloneUrls.length === 0) {
logger.debug({ npub, repoName }, 'No clone URLs found for API fetch');
return null;
}
// Try each clone URL until one works
for (const url of cloneUrls) {
try {
const metadata = await fetchRepoMetadata(url, npub, repoName);
if (metadata) {
return {
branches: metadata.branches,
defaultBranch: metadata.defaultBranch,
files: metadata.files,
commits: metadata.commits
};
}
} catch (err) {
logger.debug({ error: err, url, npub, repoName }, 'API fetch failed for URL, trying next');
continue;
}
}
return null;
} catch (err) {
logger.warn({ error: err, npub, repoName }, 'Error attempting API fetch');
return null;
}
}

3
src/routes/+page.svelte

@ -171,7 +171,8 @@ @@ -171,7 +171,8 @@
levelMessage = null;
// Show appropriate message based on level
if (levelResult.level === 'unlimited') {
const { hasUnlimitedAccess } = await import('../lib/utils/user-access.js');
if (hasUnlimitedAccess(levelResult.level)) {
levelMessage = 'Unlimited access granted!';
} else if (levelResult.level === 'rate_limited') {
levelMessage = 'Logged in with rate-limited access.';

50
src/routes/api/repos/[npub]/[repo]/branches/+server.ts

@ -54,43 +54,21 @@ export const GET: RequestHandler = createRepoGetHandler( @@ -54,43 +54,21 @@ export const GET: RequestHandler = createRepoGetHandler(
}
if (events.length > 0) {
// Try to fetch the repository from remote clone URLs
let fetched = false;
try {
fetched = await repoManager.fetchRepoOnDemand(
context.npub,
context.repo,
events[0]
);
} catch (fetchError) {
// Log the actual error for debugging
console.error('[Branches] Error in fetchRepoOnDemand:', fetchError);
// Continue to check if repo exists anyway (might have been created despite error)
}
// Try API-based fetching first (no cloning)
const { tryApiFetch } = await import('$lib/utils/api-repo-helper.js');
const apiData = await tryApiFetch(events[0], context.npub, context.repo);
// Always check if repo exists after fetch attempt (might have been created)
// Also clear cache to ensure fileManager sees it
if (existsSync(repoPath)) {
repoCache.delete(RepoCache.repoExistsKey(context.npub, context.repo));
// Repo exists, continue with normal flow
} else if (!fetched) {
// Fetch failed and repo doesn't exist
throw handleNotFoundError(
'Repository not found and could not be fetched from remote. The repository may not have any accessible clone URLs.',
{ operation: 'getBranches', npub: context.npub, repo: context.repo }
);
} else {
// Fetch returned true but repo doesn't exist - this shouldn't happen, but clear cache anyway
repoCache.delete(RepoCache.repoExistsKey(context.npub, context.repo));
// Wait a moment for filesystem to sync, then check again
await new Promise(resolve => setTimeout(resolve, 500));
if (!existsSync(repoPath)) {
throw handleNotFoundError(
'Repository fetch completed but repository is not accessible',
{ operation: 'getBranches', npub: context.npub, repo: context.repo }
);
}
if (apiData) {
// Return API data directly without cloning
return json(apiData.branches);
}
// API fetch failed - repo is not cloned and API fetch didn't work
// Return 404 with helpful message suggesting to clone
throw handleNotFoundError(
'Repository is not cloned locally and could not be fetched via API. Privileged users can clone this repository using the "Clone to Server" button.',
{ operation: 'getBranches', npub: context.npub, repo: context.repo }
);
} else {
// No events found - could be because:
// 1. Repository doesn't exist
@ -119,7 +97,7 @@ export const GET: RequestHandler = createRepoGetHandler( @@ -119,7 +97,7 @@ export const GET: RequestHandler = createRepoGetHandler(
}
}
// Double-check repo exists after on-demand fetch
// Double-check repo exists (should be true if we got here)
if (!existsSync(repoPath)) {
throw handleNotFoundError(
'Repository not found',

3
src/routes/api/repos/[npub]/[repo]/clone/+server.ts

@ -14,6 +14,7 @@ import { NostrClient } from '$lib/services/nostr/nostr-client.js'; @@ -14,6 +14,7 @@ import { NostrClient } from '$lib/services/nostr/nostr-client.js';
import { KIND } from '$lib/types/nostr.js';
import { extractRequestContext } from '$lib/utils/api-context.js';
import { getCachedUserLevel } from '$lib/services/security/user-level-cache.js';
import { hasUnlimitedAccess } from '$lib/utils/user-access.js';
import logger from '$lib/services/logger.js';
import { handleApiError, handleValidationError } from '$lib/utils/error-handler.js';
@ -38,7 +39,7 @@ export const POST: RequestHandler = async (event) => { @@ -38,7 +39,7 @@ export const POST: RequestHandler = async (event) => {
// Check if user has unlimited access
const userLevel = getCachedUserLevel(userPubkeyHex);
if (!userLevel || userLevel.level !== 'unlimited') {
if (!hasUnlimitedAccess(userLevel?.level)) {
throw error(403, 'Only users with unlimited access can clone repositories to the server.');
}

43
src/routes/api/repos/[npub]/[repo]/commits/+server.ts

@ -35,36 +35,21 @@ export const GET: RequestHandler = createRepoGetHandler( @@ -35,36 +35,21 @@ export const GET: RequestHandler = createRepoGetHandler(
]);
if (events.length > 0) {
// Try to fetch the repository from remote clone URLs
const fetched = await repoManager.fetchRepoOnDemand(
context.npub,
context.repo,
events[0]
);
// Try API-based fetching first (no cloning)
const { tryApiFetch } = await import('$lib/utils/api-repo-helper.js');
const apiData = await tryApiFetch(events[0], context.npub, context.repo);
// Always check if repo exists after fetch attempt (might have been created)
// Also clear cache to ensure fileManager sees it
if (existsSync(repoPath)) {
repoCache.delete(RepoCache.repoExistsKey(context.npub, context.repo));
// Repo exists, continue with normal flow
} else if (!fetched) {
// Fetch failed and repo doesn't exist
throw handleNotFoundError(
'Repository not found and could not be fetched from remote. The repository may not have any accessible clone URLs.',
{ operation: 'getCommits', npub: context.npub, repo: context.repo }
);
} else {
// Fetch returned true but repo doesn't exist - this shouldn't happen, but clear cache anyway
repoCache.delete(RepoCache.repoExistsKey(context.npub, context.repo));
// Wait a moment for filesystem to sync, then check again
await new Promise(resolve => setTimeout(resolve, 100));
if (!existsSync(repoPath)) {
throw handleNotFoundError(
'Repository fetch completed but repository is not accessible',
{ operation: 'getCommits', npub: context.npub, repo: context.repo }
);
}
if (apiData && apiData.commits) {
// Return API data directly without cloning
const limit = context.limit || 50;
return json(apiData.commits.slice(0, limit));
}
// API fetch failed - repo is not cloned and API fetch didn't work
throw handleNotFoundError(
'Repository is not cloned locally and could not be fetched via API. Privileged users can clone this repository using the "Clone to Server" button.',
{ operation: 'getCommits', npub: context.npub, repo: context.repo }
);
} else {
throw handleNotFoundError(
'Repository announcement not found in Nostr',
@ -86,7 +71,7 @@ export const GET: RequestHandler = createRepoGetHandler( @@ -86,7 +71,7 @@ export const GET: RequestHandler = createRepoGetHandler(
}
}
// Double-check repo exists after on-demand fetch
// Double-check repo exists (should be true if we got here)
if (!existsSync(repoPath)) {
throw handleNotFoundError(
'Repository not found',

36
src/routes/api/repos/[npub]/[repo]/download/+server.ts

@ -40,36 +40,12 @@ export const GET: RequestHandler = createRepoGetHandler( @@ -40,36 +40,12 @@ export const GET: RequestHandler = createRepoGetHandler(
]);
if (events.length > 0) {
// Try to fetch the repository from remote clone URLs
const fetched = await repoManager.fetchRepoOnDemand(
context.npub,
context.repo,
events[0]
// Download requires the actual repo files, so we can't use API fetching
// Return helpful error message
throw handleNotFoundError(
'Repository is not cloned locally. To download this repository, privileged users can clone it using the "Clone to Server" button.',
{ operation: 'download', npub: context.npub, repo: context.repo }
);
// Always check if repo exists after fetch attempt (might have been created)
// Also clear cache to ensure fileManager sees it
if (existsSync(repoPath)) {
repoCache.delete(RepoCache.repoExistsKey(context.npub, context.repo));
// Repo exists, continue with normal flow
} else if (!fetched) {
// Fetch failed and repo doesn't exist
throw handleNotFoundError(
'Repository not found and could not be fetched from remote. The repository may not have any accessible clone URLs.',
{ operation: 'download', npub: context.npub, repo: context.repo }
);
} else {
// Fetch returned true but repo doesn't exist - this shouldn't happen, but clear cache anyway
repoCache.delete(RepoCache.repoExistsKey(context.npub, context.repo));
// Wait a moment for filesystem to sync, then check again
await new Promise(resolve => setTimeout(resolve, 100));
if (!existsSync(repoPath)) {
throw handleNotFoundError(
'Repository fetch completed but repository is not accessible',
{ operation: 'download', npub: context.npub, repo: context.repo }
);
}
}
} else {
throw handleNotFoundError(
'Repository announcement not found in Nostr',
@ -91,7 +67,7 @@ export const GET: RequestHandler = createRepoGetHandler( @@ -91,7 +67,7 @@ export const GET: RequestHandler = createRepoGetHandler(
}
}
// Double-check repo exists after on-demand fetch
// Double-check repo exists (should be true if we got here)
if (!existsSync(repoPath)) {
throw handleNotFoundError(
'Repository not found',

30
src/routes/api/repos/[npub]/[repo]/file/+server.ts

@ -60,30 +60,10 @@ export const GET: RequestHandler = async ({ params, url, request }: { params: { @@ -60,30 +60,10 @@ export const GET: RequestHandler = async ({ params, url, request }: { params: {
]);
if (events.length > 0) {
// Try to fetch the repository from remote clone URLs
const fetched = await repoManager.fetchRepoOnDemand(
npub,
repo,
events[0]
);
// Always check if repo exists after fetch attempt (might have been created)
// Also clear cache to ensure fileManager sees it
if (existsSync(repoPath)) {
repoCache.delete(RepoCache.repoExistsKey(npub, repo));
// Repo exists, continue with normal flow
} else if (!fetched) {
// Fetch failed and repo doesn't exist
return error(404, 'Repository not found and could not be fetched from remote. The repository may not have any accessible clone URLs.');
} else {
// Fetch returned true but repo doesn't exist - this shouldn't happen, but clear cache anyway
repoCache.delete(RepoCache.repoExistsKey(npub, repo));
// Wait a moment for filesystem to sync, then check again
await new Promise(resolve => setTimeout(resolve, 100));
if (!existsSync(repoPath)) {
return error(404, 'Repository fetch completed but repository is not accessible');
}
}
// Try API-based fetching first (no cloning)
// For file endpoint, we can't easily fetch individual files via API without cloning
// So we return 404 with helpful message
return error(404, 'Repository is not cloned locally. To view files, privileged users can clone this repository using the "Clone to Server" button.');
} else {
return error(404, 'Repository announcement not found in Nostr');
}
@ -99,7 +79,7 @@ export const GET: RequestHandler = async ({ params, url, request }: { params: { @@ -99,7 +79,7 @@ export const GET: RequestHandler = async ({ params, url, request }: { params: {
}
}
// Double-check repo exists after on-demand fetch
// Double-check repo exists (should be true if we got here)
if (!existsSync(repoPath)) {
return error(404, 'Repository not found');
}

3
src/routes/api/repos/[npub]/[repo]/fork/+server.ts

@ -22,6 +22,7 @@ import { ResourceLimits } from '$lib/services/security/resource-limits.js'; @@ -22,6 +22,7 @@ import { ResourceLimits } from '$lib/services/security/resource-limits.js';
import { auditLogger } from '$lib/services/security/audit-logger.js';
import { ForkCountService } from '$lib/services/nostr/fork-count-service.js';
import { getCachedUserLevel } from '$lib/services/security/user-level-cache.js';
import { hasUnlimitedAccess } from '$lib/utils/user-access.js';
import logger from '$lib/services/logger.js';
import { handleApiError, handleValidationError, handleNotFoundError, handleAuthorizationError } from '$lib/utils/error-handler.js';
@ -111,7 +112,7 @@ export const POST: RequestHandler = async ({ params, request }) => { @@ -111,7 +112,7 @@ export const POST: RequestHandler = async ({ params, request }) => {
// Check if user has unlimited access (required for storing repos locally)
const userLevel = getCachedUserLevel(userPubkeyHex);
if (!userLevel || userLevel.level !== 'unlimited') {
if (!hasUnlimitedAccess(userLevel?.level)) {
const clientIp = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown';
auditLogger.logRepoFork(
userPubkeyHex,

30
src/routes/api/repos/[npub]/[repo]/readme/+server.ts

@ -45,17 +45,29 @@ export const GET: RequestHandler = createRepoGetHandler( @@ -45,17 +45,29 @@ export const GET: RequestHandler = createRepoGetHandler(
]);
if (events.length > 0) {
// Try to fetch the repository from remote clone URLs
const fetched = await repoManager.fetchRepoOnDemand(
context.npub,
context.repo,
events[0]
);
// Try API-based fetching first (no cloning)
const { tryApiFetch } = await import('$lib/utils/api-repo-helper.js');
const apiData = await tryApiFetch(events[0], context.npub, context.repo);
if (!fetched) {
// If fetch fails, return not found (readme endpoint is non-critical)
return json({ found: false });
if (apiData && apiData.files) {
// Try to find README in API files
const readmeFiles = ['README.md', 'README.markdown', 'README.txt', 'readme.md', 'readme.markdown', 'readme.txt', 'README', 'readme'];
for (const readmeFile of readmeFiles) {
const readmeFileObj = apiData.files.find(f =>
f.name.toLowerCase() === readmeFile.toLowerCase() ||
f.path.toLowerCase() === readmeFile.toLowerCase()
);
if (readmeFileObj) {
// Try to fetch README content via API
// For now, return that we found it but can't get content without cloning
// In the future, we could enhance api-repo-fetcher to fetch file content
return json({ found: false }); // Can't get content via API yet
}
}
}
// API fetch failed or README not found - return not found
return json({ found: false });
} else {
// No announcement found, return not found
return json({ found: false });

47
src/routes/api/repos/[npub]/[repo]/tree/+server.ts

@ -35,36 +35,25 @@ export const GET: RequestHandler = createRepoGetHandler( @@ -35,36 +35,25 @@ export const GET: RequestHandler = createRepoGetHandler(
]);
if (events.length > 0) {
// Try to fetch the repository from remote clone URLs
const fetched = await repoManager.fetchRepoOnDemand(
context.npub,
context.repo,
events[0]
);
// Try API-based fetching first (no cloning)
const { tryApiFetch } = await import('$lib/utils/api-repo-helper.js');
const apiData = await tryApiFetch(events[0], context.npub, context.repo);
// Always check if repo exists after fetch attempt (might have been created)
// Also clear cache to ensure fileManager sees it
if (existsSync(repoPath)) {
repoCache.delete(RepoCache.repoExistsKey(context.npub, context.repo));
// Repo exists, continue with normal flow
} else if (!fetched) {
// Fetch failed and repo doesn't exist
throw handleNotFoundError(
'Repository not found and could not be fetched from remote. The repository may not have any accessible clone URLs.',
{ operation: 'listFiles', npub: context.npub, repo: context.repo }
);
} else {
// Fetch returned true but repo doesn't exist - this shouldn't happen, but clear cache anyway
repoCache.delete(RepoCache.repoExistsKey(context.npub, context.repo));
// Wait a moment for filesystem to sync, then check again
await new Promise(resolve => setTimeout(resolve, 100));
if (!existsSync(repoPath)) {
throw handleNotFoundError(
'Repository fetch completed but repository is not accessible',
{ operation: 'listFiles', npub: context.npub, repo: context.repo }
);
}
if (apiData && apiData.files) {
// Return API data directly without cloning
const path = context.path || '';
// Filter files by path if specified
const filteredFiles = path
? apiData.files.filter(f => f.path.startsWith(path))
: apiData.files.filter(f => !f.path.includes('/') || f.path.split('/').length === 1);
return json(filteredFiles);
}
// API fetch failed - repo is not cloned and API fetch didn't work
throw handleNotFoundError(
'Repository is not cloned locally and could not be fetched via API. Privileged users can clone this repository using the "Clone to Server" button.',
{ operation: 'listFiles', npub: context.npub, repo: context.repo }
);
} else {
throw handleNotFoundError(
'Repository announcement not found in Nostr',
@ -86,7 +75,7 @@ export const GET: RequestHandler = createRepoGetHandler( @@ -86,7 +75,7 @@ export const GET: RequestHandler = createRepoGetHandler(
}
}
// Double-check repo exists after on-demand fetch
// Double-check repo exists (should be true if we got here)
if (!existsSync(repoPath)) {
throw handleNotFoundError(
'Repository not found',

3
src/routes/api/user/git-dashboard/+server.ts

@ -10,6 +10,7 @@ import type { RequestHandler } from './$types'; @@ -10,6 +10,7 @@ import type { RequestHandler } from './$types';
import { extractRequestContext } from '$lib/utils/api-context.js';
import { getAllExternalItems } from '$lib/services/git-platforms/git-platform-fetcher.js';
import { getCachedUserLevel } from '$lib/services/security/user-level-cache.js';
import { hasUnlimitedAccess } from '$lib/utils/user-access.js';
import logger from '$lib/services/logger.js';
/**
@ -27,7 +28,7 @@ export const GET: RequestHandler = async (event) => { @@ -27,7 +28,7 @@ export const GET: RequestHandler = async (event) => {
// Check user has unlimited access (same requirement as messaging forwarding)
const userLevel = getCachedUserLevel(requestContext.userPubkeyHex);
if (!userLevel || userLevel.level !== 'unlimited') {
if (!hasUnlimitedAccess(userLevel?.level)) {
return json({
issues: [],
pullRequests: [],

7
src/routes/api/user/messaging-preferences/+server.ts

@ -12,6 +12,7 @@ import type { RequestHandler } from './$types'; @@ -12,6 +12,7 @@ import type { RequestHandler } from './$types';
import { verifyEvent } from 'nostr-tools';
import { storePreferences, getPreferences, deletePreferences, hasPreferences, getRateLimitStatus } from '$lib/services/messaging/preferences-storage.server.js';
import { getCachedUserLevel } from '$lib/services/security/user-level-cache.js';
import { hasUnlimitedAccess } from '$lib/utils/user-access.js';
import { extractRequestContext } from '$lib/utils/api-context.js';
import { auditLogger } from '$lib/services/security/audit-logger.js';
import logger from '$lib/services/logger.js';
@ -87,7 +88,7 @@ export const POST: RequestHandler = async (event) => { @@ -87,7 +88,7 @@ export const POST: RequestHandler = async (event) => {
// Verify user has unlimited access
const cached = getCachedUserLevel(userPubkeyHex);
if (!cached || cached.level !== 'unlimited') {
if (!hasUnlimitedAccess(cached?.level)) {
auditLogger.log({
user: userPubkeyHex,
ip: clientIp,
@ -152,7 +153,7 @@ export const GET: RequestHandler = async (event) => { @@ -152,7 +153,7 @@ export const GET: RequestHandler = async (event) => {
// Verify user has unlimited access
const cached = getCachedUserLevel(requestContext.userPubkeyHex);
if (!cached || cached.level !== 'unlimited') {
if (!hasUnlimitedAccess(cached?.level)) {
return error(403, 'Messaging forwarding requires unlimited access level');
}
@ -189,7 +190,7 @@ export const DELETE: RequestHandler = async (event) => { @@ -189,7 +190,7 @@ export const DELETE: RequestHandler = async (event) => {
// Verify user has unlimited access
const cached = getCachedUserLevel(requestContext.userPubkeyHex);
if (!cached || cached.level !== 'unlimited') {
if (!hasUnlimitedAccess(cached?.level)) {
return error(403, 'Messaging forwarding requires unlimited access level');
}

3
src/routes/api/user/messaging-preferences/summary/+server.ts

@ -7,6 +7,7 @@ import { json, error } from '@sveltejs/kit'; @@ -7,6 +7,7 @@ import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { getPreferencesSummary } from '$lib/services/messaging/preferences-storage.server.js';
import { getCachedUserLevel } from '$lib/services/security/user-level-cache.js';
import { hasUnlimitedAccess } from '$lib/utils/user-access.js';
import { extractRequestContext } from '$lib/utils/api-context.js';
import logger from '$lib/services/logger.js';
@ -24,7 +25,7 @@ export const GET: RequestHandler = async (event) => { @@ -24,7 +25,7 @@ export const GET: RequestHandler = async (event) => {
// Verify user has unlimited access
const cached = getCachedUserLevel(requestContext.userPubkeyHex);
if (!cached || cached.level !== 'unlimited') {
if (!hasUnlimitedAccess(cached?.level)) {
return error(403, 'Messaging forwarding requires unlimited access level');
}

3
src/routes/api/user/ssh-keys/+server.ts

@ -10,6 +10,7 @@ import type { RequestHandler } from './$types'; @@ -10,6 +10,7 @@ import type { RequestHandler } from './$types';
import { extractRequestContext } from '$lib/utils/api-context.js';
import { storeAttestation, getUserAttestations, verifyAttestation, calculateSSHKeyFingerprint } from '$lib/services/ssh/ssh-key-attestation.js';
import { getCachedUserLevel } from '$lib/services/security/user-level-cache.js';
import { hasUnlimitedAccess } from '$lib/utils/user-access.js';
import { auditLogger } from '$lib/services/security/audit-logger.js';
import { verifyEvent } from 'nostr-tools';
import type { NostrEvent } from '$lib/types/nostr.js';
@ -68,7 +69,7 @@ export const POST: RequestHandler = async (event) => { @@ -68,7 +69,7 @@ export const POST: RequestHandler = async (event) => {
// Check user has unlimited access (same requirement as messaging forwarding)
const userLevel = getCachedUserLevel(requestContext.userPubkeyHex);
if (!userLevel || userLevel.level !== 'unlimited') {
if (!hasUnlimitedAccess(userLevel?.level)) {
return error(403, 'SSH key attestation requires unlimited access. Please verify you can write to at least one default Nostr relay.');
}

4
src/routes/signup/+page.svelte

@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
import type { NostrEvent } from '../../lib/types/nostr.js';
import { nip19 } from 'nostr-tools';
import { userStore } from '../../lib/stores/user-store.js';
import { hasUnlimitedAccess } from '../../lib/utils/user-access.js';
import { hasUnlimitedAccess, isLoggedIn } from '../../lib/utils/user-access.js';
let nip07Available = $state(false);
let loading = $state(false);
@ -2197,7 +2197,7 @@ @@ -2197,7 +2197,7 @@
<div class="form-actions">
<button
type="submit"
disabled={loading || !nip07Available}
disabled={loading || !nip07Available || !isLoggedIn($userStore.userLevel) || !hasUnlimitedAccess($userStore.userLevel)}
>
{loading ? 'Publishing...' : 'Publish Repository Announcement'}
</button>

Loading…
Cancel
Save