This repository was archived by the owner on Mar 9, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 47
feat(dApp): Added Git identity binding during member registration. #224
Open
theobiabo
wants to merge
14
commits into
tupui:main
Choose a base branch
from
theobiabo:feature/git_identity_binding
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
0d0ca64
feat (Git): Added Git identity binding during member registration.
theobiabo feb9653
fixed merge conflict
theobiabo e4e92de
Merge branch 'main' of https://github.com/yhoungdev/soroban-versionin…
theobiabo 525d223
chore: updated suggestions
theobiabo d892caa
fixing conflict
theobiabo 62e5f3b
fixed undefined Gitverevification function error
theobiabo 7cce6c1
bug: fixed Github binding CORs issues
theobiabo f3c8957
fixing local conflict
theobiabo 424e165
bug: fixed merge conflict
theobiabo f6ad36c
chore(cmd): removed /dev/stdin.
theobiabo d83d280
enhance(Signature): optimized signature handling flow
theobiabo dc3a031
bug: fixing parseSSHSignature
theobiabo 979263d
chore: added copybutton and hide pgp if option is ssh
theobiabo 09f5409
chore: added sshsig for handling ssh verification
theobiabo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,281 @@ | ||
| import { useState, type FC } from "react"; | ||
| import Input from "./Input"; | ||
| import Button from "./Button"; | ||
| import { | ||
| parseGitHandle, | ||
| fetchGitPublicKeys, | ||
| extractEd25519PublicKey, | ||
| createSEP53Envelope, | ||
| generateSSHSignCommand, | ||
| generateGPGSignCommand, | ||
| parseSSHSignature, | ||
| validateGitVerification, | ||
| type GitVerificationData | ||
| } from "../../utils/gitVerification"; | ||
| import CopyButton from "./CopyButton"; | ||
| import { toast } from "utils/utils"; | ||
|
|
||
| interface GitVerificationProps { | ||
| onVerificationComplete: (data: GitVerificationData | null) => void; | ||
| networkPassphrase: string; | ||
| signingAccount: string; | ||
| contractId: string; | ||
| } | ||
|
|
||
| const GitVerification: FC<GitVerificationProps> = ({ | ||
| onVerificationComplete, | ||
| networkPassphrase, | ||
| signingAccount, | ||
| contractId | ||
| }) => { | ||
|
|
||
| const [wantGitLink, setWantGitLink] = useState<boolean | null>(null); | ||
| const [gitHandle, setGitHandle] = useState(""); | ||
| const [selectedKey, setSelectedKey] = useState(""); | ||
| const [availableKeys, setAvailableKeys] = useState<string[]>([]); | ||
| const [envelope, setEnvelope] = useState(""); | ||
| const [signature, setSignature] = useState(""); | ||
| const [isLoadingKeys, setIsLoadingKeys] = useState(false); | ||
| const [isVerifying, setIsVerifying] = useState(false); | ||
| const [step, setStep] = useState<"choice" | "handle" | "key" | "sign" | "verify">("choice"); | ||
|
|
||
| const handleGitHandleSubmit = async () => { | ||
| if (!gitHandle.trim()) { | ||
| toast.error("Error", "Please enter a Git handle"); | ||
| return; | ||
| } | ||
|
|
||
| const parsed = parseGitHandle(gitHandle.trim()); | ||
| if (!parsed) { | ||
| toast.error("Error", "Invalid Git handle format. Use provider:username (e.g., github:alice)"); | ||
| return; | ||
| } | ||
|
|
||
| setIsLoadingKeys(true); | ||
| try { | ||
| const keys = await fetchGitPublicKeys(gitHandle.trim()); | ||
| setAvailableKeys(keys); | ||
| setStep("key"); | ||
| } catch (error) { | ||
| toast.error("Error", error instanceof Error ? error.message : "Failed to fetch Git keys"); | ||
| } finally { | ||
| setIsLoadingKeys(false); | ||
| } | ||
| }; | ||
|
|
||
| const handleKeySelection = () => { | ||
| if (!selectedKey) { | ||
| toast.error("Error", "Please select a public key"); | ||
| return; | ||
| } | ||
|
|
||
| // if (!contractId) { | ||
| // toast.error("Error", "Contract ID is not available. Please try again later."); | ||
| // return; | ||
| // } | ||
|
|
||
| const generatedEnvelope = createSEP53Envelope( | ||
| networkPassphrase, | ||
| signingAccount, | ||
| contractId, | ||
| gitHandle.trim() | ||
| ); | ||
| setEnvelope(generatedEnvelope); | ||
| setStep("sign"); | ||
| }; | ||
|
|
||
| const handleVerifySignature = async () => { | ||
| if (!signature.trim()) { | ||
| toast.error("Error", "Please paste your signature"); | ||
| return; | ||
| } | ||
|
|
||
| setIsVerifying(true); | ||
| try { | ||
| const publicKey = extractEd25519PublicKey(selectedKey); | ||
| const parsedSignature = parseSSHSignature(signature.trim()); | ||
|
|
||
| const verificationData: GitVerificationData = { | ||
| gitHandle: gitHandle.trim(), | ||
| publicKey, | ||
| envelope, | ||
| signature: parsedSignature | ||
| }; | ||
|
|
||
| const validation = await validateGitVerification( | ||
| verificationData, | ||
| networkPassphrase, | ||
| signingAccount, | ||
| contractId | ||
| ); | ||
|
|
||
| console.log("Git verification result:", validation); | ||
|
|
||
| if (validation.valid) { | ||
| toast.success("Success", "Git verification completed successfully!"); | ||
| onVerificationComplete(verificationData); | ||
| } else { | ||
| toast.error("Verification Failed", validation.error || "Unknown error"); | ||
| } | ||
| } catch (error) { | ||
| toast.error("Error", error instanceof Error ? error.message : "Verification failed"); | ||
| } finally { | ||
| setIsVerifying(false); | ||
| } | ||
| }; | ||
|
|
||
| const handleSkip = () => { | ||
| setWantGitLink(false); | ||
| onVerificationComplete(null); | ||
| }; | ||
|
|
||
| if (wantGitLink === false) { | ||
| return null; | ||
| } | ||
|
|
||
| return ( | ||
| <div className="border-t pt-4 mt-4"> | ||
| <h3 className="text-lg font-semibold text-primary mb-4">Git Handle Verification</h3> | ||
|
|
||
| {step === "choice" && ( | ||
| <div className="space-y-4"> | ||
| <p className="text-sm text-secondary"> | ||
| Would you like to link a Git handle to your account for verification? | ||
| </p> | ||
| <div className="flex gap-3"> | ||
| <Button type="primary" onClick={() => { setWantGitLink(true); setStep("handle"); }}> | ||
| Yes, Link Git Handle | ||
| </Button> | ||
| <Button type="secondary" onClick={handleSkip}> | ||
| Skip | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| )} | ||
|
|
||
| {step === "handle" && ( | ||
| <div className="space-y-4"> | ||
| <Input | ||
| label="Git Handle" | ||
| placeholder="github:alice or gitlab:bob" | ||
| value={gitHandle} | ||
| onChange={(e) => setGitHandle(e.target.value)} | ||
| helpText="Format: provider:username (supports GitHub and GitLab)" | ||
| /> | ||
| <div className="flex gap-3"> | ||
| <Button | ||
| type="primary" | ||
| onClick={handleGitHandleSubmit} | ||
| isLoading={isLoadingKeys} | ||
| > | ||
| Fetch Public Keys | ||
| </Button> | ||
| <Button type="secondary" onClick={() => setStep("choice")}> | ||
| Back | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| )} | ||
|
|
||
| {step === "key" && ( | ||
| <div className="space-y-4"> | ||
| <div> | ||
| <label className="block text-sm font-medium text-primary mb-2"> | ||
| Select Ed25519 Public Key | ||
| </label> | ||
| <div className="space-y-2 max-h-40 overflow-y-auto"> | ||
| {availableKeys.map((key, index) => ( | ||
| <label key={index} className="flex items-start gap-2 p-2 border rounded cursor-pointer hover:bg-gray-50"> | ||
| <input | ||
| type="radio" | ||
| name="publicKey" | ||
| value={key} | ||
| checked={selectedKey === key} | ||
| onChange={(e) => setSelectedKey(e.target.value)} | ||
| className="mt-1" | ||
| /> | ||
| <span className="text-xs font-mono break-all">{key}</span> | ||
| </label> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| <div className="flex gap-3"> | ||
| <Button type="primary" onClick={handleKeySelection}> | ||
| Continue | ||
| </Button> | ||
| <Button type="secondary" onClick={() => setStep("handle")}> | ||
| Back | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| )} | ||
|
|
||
| {step === "sign" && ( | ||
| <div className="space-y-4"> | ||
| <div> | ||
| <label className="block text-sm font-medium text-primary mb-2"> | ||
| SEP-53 Envelope to Sign | ||
| </label> | ||
| <textarea | ||
| value={envelope} | ||
| readOnly | ||
| className="w-full p-3 border rounded font-mono text-xs bg-gray-50" | ||
| rows={6} | ||
| /> | ||
| </div> | ||
|
|
||
| <div className="bg-blue-50 p-4 rounded"> | ||
| <h4 className="font-medium text-blue-900 mb-2">Sign this envelope with your Git key:</h4> | ||
| <div className="space-y-2"> | ||
| <div className="flex items-start gap-2"> | ||
| <div className="flex-1"> | ||
| <p className="text-sm text-blue-800 mb-1">SSH Command:</p> | ||
| <code className="block p-2 bg-blue-100 rounded text-xs break-all"> | ||
| {generateSSHSignCommand(envelope).replace(/\s*\/dev\/stdin\s*/g, "")} | ||
| </code> | ||
| </div> | ||
| <CopyButton textToCopy={generateSSHSignCommand(envelope).replace(/\s*\/dev\/stdin\s*/g, "")} size="sm" /> | ||
| </div> | ||
| {/* Only show GPG command if not using SSH key (future extensibility) */} | ||
| {/* | ||
| <div className="flex items-start gap-2"> | ||
| <div className="flex-1"> | ||
| <p className="text-sm text-blue-800 mb-1">GPG Command:</p> | ||
| <code className="block p-2 bg-blue-100 rounded text-xs break-all"> | ||
| {generateGPGSignCommand(envelope)} | ||
| </code> | ||
| </div> | ||
| <CopyButton textToCopy={generateGPGSignCommand(envelope)} size="sm" /> | ||
| </div> | ||
| */} | ||
| </div> | ||
| </div> | ||
|
|
||
| <Input | ||
| label="Paste Signature" | ||
| placeholder="Paste the signature output here..." | ||
| value={signature} | ||
| onChange={(e) => setSignature(e.target.value)} | ||
| multiline | ||
| rows={6} | ||
| /> | ||
|
|
||
| <div className="flex gap-3"> | ||
| <Button | ||
| type="primary" | ||
| onClick={handleVerifySignature} | ||
| isLoading={isVerifying} | ||
| > | ||
| Verify Signature | ||
| </Button> | ||
| <Button type="secondary" onClick={() => setStep("key")}> | ||
| Back | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default GitVerification; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I am not sure we need all that to be in the contract itself vs the meta file-if even needed. I would keep the handle and key only. And in meta nothing. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry i don't get you clearly
you mean i should remove this
git_identity?: string; git_pubkey?: Buffer; msg?: string; sig?: Buffer; signed_at?: u64;?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes we just need the identity and key to be stored in the contract. When I will call the smart contract function
add_memberI will pass everything though as to validate on-chain the data.