Browse Source

fix verification badge

main
Silberengel 4 weeks ago
parent
commit
17457b78d8
  1. 2
      README.md
  2. 40
      docs/GIT_CREDENTIAL_HELPER.md
  3. 1
      scripts/Untitled
  4. 12
      scripts/git-credential-nostr.js
  5. 4
      src/lib/services/nostr/ownership-transfer-service.ts
  6. 111
      src/routes/repos/[npub]/[repo]/+page.svelte
  7. 42
      vite.config.ts

2
README.md

@ -351,7 +351,7 @@ See `docs/SECURITY.md` and `docs/SECURITY_IMPLEMENTATION.md` for detailed inform @@ -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`)

40
docs/GIT_CREDENTIAL_HELPER.md

@ -14,7 +14,7 @@ GitRepublic uses NIP-98 HTTP Authentication for git operations. The credential h @@ -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 @@ -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="<your-64-char-hex-private-key>"
export NOSTRGIT_SECRET_KEY="<your-64-char-hex-private-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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 <base64-event>` header
@ -168,14 +168,14 @@ git pull origin main @@ -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 @@ -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

1
scripts/Untitled

@ -0,0 +1 @@ @@ -0,0 +1 @@
git config --global credential.http://localhost.helper '!node /absolute/path/to/gitrepublic-web/scripts/git-credential-nostr.js'

12
scripts/git-credential-nostr.js

@ -13,9 +13,9 @@ @@ -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() { @@ -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="<hex-key>"');
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="<hex-key>"');
process.exit(1);
}

4
src/lib/services/nostr/ownership-transfer-service.ts

@ -40,7 +40,7 @@ export class OwnershipTransferService { @@ -40,7 +40,7 @@ export class OwnershipTransferService {
*/
async getCurrentOwnerFromRepo(npub: string, repoId: string): Promise<string | null> {
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 { @@ -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));

111
src/routes/repos/[npub]/[repo]/+page.svelte

@ -154,7 +154,12 @@ @@ -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<string | null>(null);
let loadingVerification = $state(false);
@ -1480,13 +1485,18 @@ @@ -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 @@ @@ -2774,7 +2784,54 @@
<div class="repo-clone-urls">
<span class="clone-label">Clone:</span>
{#each pageData.repoCloneUrls.slice(0, 3) as 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;
})}
<div class="clone-url-wrapper">
<code class="clone-url">{cloneUrl}</code>
{#if loadingVerification}
<span class="verification-badge loading" title="Checking verification...">
<span style="opacity: 0.5;"></span>
</span>
{:else if cloneVerification !== undefined}
<span
class="verification-badge"
class:verified={cloneVerification.verified}
class:unverified={!cloneVerification.verified}
title={cloneVerification.verified ? 'Verified ownership' : (cloneVerification.error || 'Unverified')}
>
{#if cloneVerification.verified}
<img src="/icons/check-circle.svg" alt="Verified" class="icon-inline" />
{:else}
<img src="/icons/alert-triangle.svg" alt="Unverified" class="icon-inline" />
{/if}
</span>
{:else if verificationStatus}
{#if verificationStatus.cloneVerifications && verificationStatus.cloneVerifications.length > 0}
<span class="verification-badge unverified" title="Verification status unknown for this clone">
<img src="/icons/alert-triangle.svg" alt="Unknown" class="icon-inline" />
</span>
{:else}
<span class="verification-badge unverified" title="Verification not available for this clone">
<img src="/icons/alert-triangle.svg" alt="Not verified" class="icon-inline" />
</span>
{/if}
{:else}
<span class="verification-badge unverified" title="Verification not checked">
<img src="/icons/alert-triangle.svg" alt="Not checked" class="icon-inline" />
</span>
{/if}
</div>
{/each}
{#if pageData.repoCloneUrls.length > 3}
<span class="clone-more">+{pageData.repoCloneUrls.length - 3} more</span>
@ -2861,17 +2918,6 @@ @@ -2861,17 +2918,6 @@
{/if}
{/if}
</div>
{#if verificationStatus}
<span class="verification-status" class:verified={verificationStatus.verified} class:unverified={!verificationStatus.verified}>
{#if verificationStatus.verified}
<img src="/icons/check-circle.svg" alt="Verified Repo Ownership" class="icon-inline" />
Verified Repo Ownership
{:else}
<img src="/icons/alert-triangle.svg" alt="Unverified" class="icon-inline" />
Unverified
{/if}
</span>
{/if}
</div>
</div>
</header>
@ -4533,6 +4579,12 @@ @@ -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 @@ @@ -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 @@ @@ -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%);
}

42
vite.config.ts

@ -46,14 +46,52 @@ export default defineConfig({ @@ -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 (

Loading…
Cancel
Save