diff --git a/README.md b/README.md index b7c36a3..29576e8 100644 --- a/README.md +++ b/README.md @@ -351,7 +351,7 @@ See `docs/SECURITY.md` and `docs/SECURITY_IMPLEMENTATION.md` for detailed inform ## Environment Variables -- `NOSTRGIT_SECRET_KEY_CLIENT`: User's nsec (bech32 or hex) for client-side git operations via credential helper (optional) +- `NOSTRGIT_SECRET_KEY`: User's nsec (bech32 or hex) for client-side git operations via credential helper (optional) - `GIT_REPO_ROOT`: Path to store git repositories (default: `/repos`) - `GIT_DOMAIN`: Domain for git repositories (default: `localhost:6543`) - `NOSTR_RELAYS`: Comma-separated list of Nostr relays (default: `wss://theforest.nostr1.com`) diff --git a/docs/GIT_CREDENTIAL_HELPER.md b/docs/GIT_CREDENTIAL_HELPER.md index 0faaa8a..007ef29 100644 --- a/docs/GIT_CREDENTIAL_HELPER.md +++ b/docs/GIT_CREDENTIAL_HELPER.md @@ -14,7 +14,7 @@ GitRepublic uses NIP-98 HTTP Authentication for git operations. The credential h chmod +x scripts/git-credential-nostr.js ``` -### 2. Set your NOSTRGIT_SECRET_KEY_CLIENT environment variable +### 2. Set your NOSTRGIT_SECRET_KEY environment variable **Important:** - This is YOUR user private key (for authenticating your git operations) @@ -22,14 +22,14 @@ chmod +x scripts/git-credential-nostr.js ```bash # Option 1: Export in your shell session -export NOSTRGIT_SECRET_KEY_CLIENT="nsec1..." +export NOSTRGIT_SECRET_KEY="nsec1..." # Option 2: Add to your ~/.bashrc or ~/.zshrc (for persistent setup) -echo 'export NOSTRGIT_SECRET_KEY_CLIENT="nsec1..."' >> ~/.bashrc +echo 'export NOSTRGIT_SECRET_KEY="nsec1..."' >> ~/.bashrc source ~/.bashrc # Option 3: Use a hex private key (64 characters) -export NOSTRGIT_SECRET_KEY_CLIENT="" +export NOSTRGIT_SECRET_KEY="" # Note: The script also supports NOSTR_PRIVATE_KEY and NSEC for backward compatibility ``` @@ -88,7 +88,7 @@ git clone https://your-domain.com/npub1abc123.../my-repo.git git clone http://localhost:5173/api/git/npub1abc123.../my-repo.git ``` -The credential helper will automatically generate a NIP-98 auth token using your NOSTRGIT_SECRET_KEY_CLIENT. +The credential helper will automatically generate a NIP-98 auth token using your NOSTRGIT_SECRET_KEY. ## Localhost Setup Example @@ -102,10 +102,10 @@ npm run dev # Server runs on http://localhost:5173 ``` -### 2. Set your NOSTRGIT_SECRET_KEY_CLIENT +### 2. Set your NOSTRGIT_SECRET_KEY ```bash -export NOSTRGIT_SECRET_KEY_CLIENT="nsec1..." +export NOSTRGIT_SECRET_KEY="nsec1..." ``` ### 3. Configure git for localhost @@ -131,12 +131,12 @@ git clone http://localhost:5173/api/git/npub1abc123.../my-repo.git cd my-repo # If you need to add the remote manually -git remote add origin http://localhost:5173/api/git/npub1abc123.../my-repo.git +git remote add gitrepublic-web http://localhost:5173/api/git/npub1abc123.../my-repo.git # Make some changes and push git add . git commit -m "Initial commit" -git push -u origin main +git push -u gitrepublic-web main ``` **Note:** The git HTTP backend endpoint is `/api/git/`, so the full URL format is: @@ -145,7 +145,7 @@ git push -u origin main ### Push changes ```bash -git push origin main +git push gitrepublic-web main ``` The credential helper will generate the appropriate NIP-98 auth token for push operations. @@ -153,14 +153,14 @@ The credential helper will generate the appropriate NIP-98 auth token for push o ### Fetch/Pull ```bash -git fetch origin -git pull origin main +git fetch gitrepublic-web +git pull gitrepublic-web main ``` ## How It Works 1. When git needs credentials, it calls the credential helper with the repository URL -2. The helper reads your `NOSTRGIT_SECRET_KEY_CLIENT` environment variable (with fallbacks for backward compatibility) +2. The helper reads your `NOSTRGIT_SECRET_KEY` environment variable (with fallbacks for backward compatibility) 3. It creates a NIP-98 authentication event signed with your private key 4. The signed event is base64-encoded and returned as the "password" 5. Git sends this in the `Authorization: Nostr ` header @@ -168,14 +168,14 @@ git pull origin main ## Troubleshooting -### Error: NOSTRGIT_SECRET_KEY_CLIENT environment variable is not set +### Error: NOSTRGIT_SECRET_KEY environment variable is not set -Make sure you've exported the NOSTRGIT_SECRET_KEY_CLIENT variable: +Make sure you've exported the NOSTRGIT_SECRET_KEY variable: ```bash -export NOSTRGIT_SECRET_KEY_CLIENT="nsec1..." +export NOSTRGIT_SECRET_KEY="nsec1..." ``` -**Note:** The script also supports `NOSTR_PRIVATE_KEY` and `NSEC` for backward compatibility, but `NOSTRGIT_SECRET_KEY_CLIENT` is the preferred name. +**Note:** The script also supports `NOSTR_PRIVATE_KEY` and `NSEC` for backward compatibility, but `NOSTRGIT_SECRET_KEY` is the preferred name. ### Error: Invalid nsec format @@ -195,12 +195,12 @@ Push operations require POST authentication. The credential helper automatically 1. Verify you have maintainer permissions for the repository 2. Check that branch protection rules allow your push -3. Ensure your NOSTRGIT_SECRET_KEY_CLIENT is correctly set +3. Ensure your NOSTRGIT_SECRET_KEY is correctly set ## Security Best Practices -1. **Never commit your NOSTRGIT_SECRET_KEY_CLIENT to version control** - - Add `NOSTRGIT_SECRET_KEY_CLIENT` to your `.gitignore` if you store it in a file +1. **Never commit your NOSTRGIT_SECRET_KEY to version control** + - Add `NOSTRGIT_SECRET_KEY` to your `.gitignore` if you store it in a file - Use environment variables instead of hardcoding - **Important:** This is YOUR user key for client-side operations diff --git a/scripts/Untitled b/scripts/Untitled new file mode 100644 index 0000000..b948eed --- /dev/null +++ b/scripts/Untitled @@ -0,0 +1 @@ +git config --global credential.http://localhost.helper '!node /absolute/path/to/gitrepublic-web/scripts/git-credential-nostr.js' diff --git a/scripts/git-credential-nostr.js b/scripts/git-credential-nostr.js index ec9f9c4..b9e9035 100755 --- a/scripts/git-credential-nostr.js +++ b/scripts/git-credential-nostr.js @@ -13,9 +13,9 @@ * git config --global credential.https://your-domain.com.helper '!node /path/to/gitrepublic-web/scripts/git-credential-nostr.js' * * Environment variables: - * NOSTRGIT_SECRET_KEY_CLIENT - Your Nostr private key (nsec format or hex) for client-side git operations + * NOSTRGIT_SECRET_KEY - Your Nostr private key (nsec format or hex) for client-side git operations * - * Security: Keep your NOSTRGIT_SECRET_KEY_CLIENT secure and never commit it to version control! + * Security: Keep your NOSTRGIT_SECRET_KEY secure and never commit it to version control! */ import { createHash } from 'crypto'; @@ -124,11 +124,11 @@ async function main() { // For 'get' command, generate credentials if (command === 'get') { // Get private key from environment variable - // Support NOSTRGIT_SECRET_KEY_CLIENT (preferred), with fallbacks for backward compatibility - const nsec = process.env.NOSTRGIT_SECRET_KEY_CLIENT || process.env.NOSTR_PRIVATE_KEY || process.env.NSEC; + // Support NOSTRGIT_SECRET_KEY (preferred), with fallbacks for backward compatibility + const nsec = process.env.NOSTRGIT_SECRET_KEY || process.env.NOSTR_PRIVATE_KEY || process.env.NSEC; if (!nsec) { - console.error('Error: NOSTRGIT_SECRET_KEY_CLIENT environment variable is not set'); - console.error('Set it with: export NOSTRGIT_SECRET_KEY_CLIENT="nsec1..." or NOSTRGIT_SECRET_KEY_CLIENT=""'); + console.error('Error: NOSTRGIT_SECRET_KEY environment variable is not set'); + console.error('Set it with: export NOSTRGIT_SECRET_KEY="nsec1..." or NOSTRGIT_SECRET_KEY=""'); process.exit(1); } diff --git a/src/lib/services/nostr/ownership-transfer-service.ts b/src/lib/services/nostr/ownership-transfer-service.ts index 8efcf81..42b8d5f 100644 --- a/src/lib/services/nostr/ownership-transfer-service.ts +++ b/src/lib/services/nostr/ownership-transfer-service.ts @@ -40,7 +40,7 @@ export class OwnershipTransferService { */ async getCurrentOwnerFromRepo(npub: string, repoId: string): Promise { try { - const { fileManager } = await import('../services/service-registry.js'); + const { fileManager } = await import('../service-registry.js'); return await fileManager.getCurrentOwnerFromRepo(npub, repoId); } catch (error) { logger.error({ error, npub, repoId }, 'Error getting current owner from repo'); @@ -81,7 +81,7 @@ export class OwnershipTransferService { try { const { nip19 } = await import('nostr-tools'); const npub = nip19.npubEncode(announcementEvent.pubkey); - const { fileManager } = await import('../services/service-registry.js'); + const { fileManager } = await import('../service-registry.js'); const localOwner = await fileManager.getCurrentOwnerFromRepo(npub, dTag); const localUrl = cloneUrls.find(url => url.includes(npub) || url.includes(announcementEvent.pubkey)); diff --git a/src/routes/repos/[npub]/[repo]/+page.svelte b/src/routes/repos/[npub]/[repo]/+page.svelte index 66d9cf2..a7ff655 100644 --- a/src/routes/repos/[npub]/[repo]/+page.svelte +++ b/src/routes/repos/[npub]/[repo]/+page.svelte @@ -154,7 +154,12 @@ const cloneTooltip = 'Please clone this repo to use this feature.'; // Verification status - let verificationStatus = $state<{ verified: boolean; error?: string; message?: string } | null>(null); + let verificationStatus = $state<{ + verified: boolean; + error?: string; + message?: string; + cloneVerifications?: Array<{ url: string; verified: boolean; ownerPubkey: string | null; error?: string }>; + } | null>(null); let showVerificationDialog = $state(false); let verificationFileContent = $state(null); let loadingVerification = $state(false); @@ -1480,13 +1485,18 @@ }); if (response.ok) { const data = await response.json(); + console.log('[Verification] Response:', data); verificationStatus = data; + } else { + console.warn('[Verification] Response not OK:', response.status, response.statusText); + verificationStatus = { verified: false, error: `Verification check failed: ${response.status}` }; } } catch (err) { - console.error('Failed to check verification:', err); + console.error('[Verification] Failed to check verification:', err); verificationStatus = { verified: false, error: 'Failed to check verification' }; } finally { loadingVerification = false; + console.log('[Verification] Status after check:', verificationStatus); } } @@ -2774,7 +2784,54 @@
Clone: {#each pageData.repoCloneUrls.slice(0, 3) as cloneUrl} - {cloneUrl} + {@const cloneVerification = verificationStatus?.cloneVerifications?.find(cv => { + // Match URLs more flexibly (handle trailing slashes, http/https differences) + const normalizeUrl = (url: string) => url.replace(/\/$/, '').toLowerCase().replace(/^https?:\/\//, ''); + const normalizedCv = normalizeUrl(cv.url); + const normalizedClone = normalizeUrl(cloneUrl); + const matches = normalizedCv === normalizedClone || + normalizedCv.includes(normalizedClone) || + normalizedClone.includes(normalizedCv); + if (matches) { + console.log('[Verification] Matched clone URL:', cloneUrl, 'with verification:', cv); + } + return matches; + })} +
+ {cloneUrl} + {#if loadingVerification} + + + + {:else if cloneVerification !== undefined} + + {#if cloneVerification.verified} + Verified + {:else} + Unverified + {/if} + + {:else if verificationStatus} + {#if verificationStatus.cloneVerifications && verificationStatus.cloneVerifications.length > 0} + + Unknown + + {:else} + + Not verified + + {/if} + {:else} + + Not checked + + {/if} +
{/each} {#if pageData.repoCloneUrls.length > 3} +{pageData.repoCloneUrls.length - 3} more @@ -2861,17 +2918,6 @@ {/if} {/if}
- {#if verificationStatus} - - {#if verificationStatus.verified} - Verified Repo Ownership - Verified Repo Ownership - {:else} - Unverified - Unverified - {/if} - - {/if} @@ -4533,6 +4579,12 @@ font-weight: 500; } + .clone-url-wrapper { + display: inline-flex; + align-items: center; + gap: 0.5rem; + } + .clone-url { padding: 0.125rem 0.375rem; background: var(--bg-secondary); @@ -5786,22 +5838,31 @@ color: var(--text-primary); } - .verification-status { - padding: 0.25rem 0.5rem; + .verification-badge { + display: inline-flex; + align-items: center; + padding: 0.125rem 0.25rem; border-radius: 0.25rem; font-size: 0.75rem; - font-weight: 500; - margin-left: 0.5rem; + flex-shrink: 0; } - .verification-status.verified { - background: var(--success-bg); - color: var(--success-text); + .verification-badge.loading { + opacity: 0.6; } - .verification-status.unverified { - background: var(--error-bg); - color: var(--error-text); + .verification-badge.verified { + color: var(--success-text, #10b981); + } + + .verification-badge.unverified { + color: var(--error-text, #f59e0b); + } + + .verification-badge .icon-inline { + width: 1em; + height: 1em; + margin: 0; } .icon-inline { @@ -5824,12 +5885,12 @@ /* Theme-aware icon colors */ - .verification-status.verified .icon-inline { + .verification-badge.verified .icon-inline { /* Green checkmark for verified */ filter: brightness(0) saturate(100%) invert(48%) sepia(79%) saturate(2476%) hue-rotate(86deg) brightness(118%) contrast(119%); } - .verification-status.unverified .icon-inline { + .verification-badge.unverified .icon-inline { /* Orange/yellow warning for unverified */ filter: brightness(0) saturate(100%) invert(67%) sepia(93%) saturate(1352%) hue-rotate(358deg) brightness(102%) contrast(106%); } diff --git a/vite.config.ts b/vite.config.ts index 07c40d1..2be9d33 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -46,14 +46,52 @@ export default defineConfig({ ssr: { // Exclude Node.js-only modules from client bundle noExternal: [], - external: [] + external: [ + 'simple-git', + 'child_process', + 'fs', + 'fs/promises', + 'path', + 'os', + 'crypto', + 'stream', + 'util', + 'events', + 'buffer' + ] }, optimizeDeps: { // Exclude server-only modules from pre-bundling - exclude: ['src/lib/services/messaging/preferences-storage.ts'] + exclude: [ + 'src/lib/services/messaging/preferences-storage.ts', + 'simple-git' + ] }, build: { rollupOptions: { + external: (id) => { + // Externalize Node.js-only modules to prevent bundling for browser + if (id === 'simple-git' || id.startsWith('simple-git/')) { + return true; + } + // Externalize Node.js built-in modules + if ( + id === 'child_process' || + id === 'fs' || + id === 'fs/promises' || + id === 'path' || + id === 'os' || + id === 'crypto' || + id === 'stream' || + id === 'util' || + id === 'events' || + id === 'buffer' || + id.startsWith('node:') + ) { + return true; + } + return false; + }, onwarn(warning, warn) { // Suppress warnings about externalized modules (expected for SSR builds) if (