-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: implement ICP verfiable Credential with Dacade as relying party #1284
Changes from 2 commits
3eb9e96
d827bff
e097bda
d0f58e7
7edf22b
79842a9
afec598
0da6c3f
3f9230d
caae4c5
41812f4
2e1ccad
5617aa6
3dda8c0
aa11662
c8b19b6
356aed3
0228735
5827fc3
8b8550a
9d19e81
5b424b2
797ae20
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
v20.14.0 | ||
v22.6.0 |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,77 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { Identity } from "@dfinity/agent"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { AuthClient } from "@dfinity/auth-client"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { Principal } from "@dfinity/principal"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { useEffect } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
type Auth = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
client: AuthClient; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
isAuthenticated: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
identity: Identity; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
principal: Principal; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
principalText: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
declare global { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
interface Window { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
auth: Auth; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
canister: any; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export const MAX_TTL = BigInt(7 * 24 * 60 * 60 * 1000 * 1000 * 1000); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* For production ready we shall use https://identity.ic0.app/ as identity provider | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export const IDENTITY_PROVIDER = "https://fgte5-ciaaa-aaaad-aaatq-cai.ic0.app/"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const useIcpAuth = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!window) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use To prevent potential Apply this diff to fix the issue: - if (!window) return;
+ if (typeof window === 'undefined') return; 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
async function initializeContract() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const authClient = await AuthClient.create(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
window.auth = {} as any; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
window.canister = {}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
window.auth.client = authClient; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
window.auth.isAuthenticated = await authClient.isAuthenticated(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
window.auth.identity = authClient.getIdentity(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
window.auth.principal = authClient.getIdentity()?.getPrincipal(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
window.auth.principalText = authClient.getIdentity()?.getPrincipal().toText(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
initializeContract(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, []); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure proper error handling in The async function initializeContract() {
try {
const authClient = await AuthClient.create();
window.auth = {} as Auth;
window.canister = {};
window.auth.client = authClient;
window.auth.isAuthenticated = await authClient.isAuthenticated();
window.auth.identity = authClient.getIdentity();
window.auth.principal = authClient.getIdentity()?.getPrincipal();
window.auth.principalText = authClient.getIdentity()?.getPrincipal().toText();
} catch (error) {
console.error("Failed to initialize contract:", error);
}
} Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
async function login(callback: (principal: string) => void) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!window) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const authClient = window.auth.client; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const isAuthenticated = await authClient.isAuthenticated(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!isAuthenticated) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
await authClient?.login({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
maxTimeToLive: MAX_TTL, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
identityProvider: IDENTITY_PROVIDER, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
onSuccess: async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
window.auth.isAuthenticated = await authClient.isAuthenticated(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const principal = await authClient.getIdentity()?.getPrincipal().toText(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
callback(principal); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
serapieTuyishime marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const principal = await authClient.getIdentity()?.getPrincipal().toText(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
callback(principal); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle potential errors in The async function login(callback: (principal: string) => void) {
if (!window) return;
const authClient = window.auth.client;
try {
const isAuthenticated = await authClient.isAuthenticated();
if (!isAuthenticated) {
await authClient?.login({
maxTimeToLive: MAX_TTL,
identityProvider: IDENTITY_PROVIDER,
onSuccess: async () => {
window.auth.isAuthenticated = await authClient.isAuthenticated();
const principal = await authClient.getIdentity()?.getPrincipal().toText();
callback(principal);
},
});
} else {
const principal = await authClient.getIdentity()?.getPrincipal().toText();
callback(principal);
}
} catch (error) {
console.error("Login failed:", error);
}
} Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
async function logout() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!window) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const authClient = window.auth.client; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
authClient.logout(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+88
to
+92
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure As with the Consider adding a check to confirm Await the The Apply this diff to fix the issue: async function logout() {
if (typeof window === 'undefined') return;
const authClient = window.auth.client;
- authClient.logout();
+ await authClient.logout();
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
login, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logout, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
serapieTuyishime marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export default useIcpAuth; |
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -21,6 +21,10 @@ import MintCertificate from "@/components/sections/profile/modals/MintCertificat | |||||||||||||||||
import { Certificate } from "@/types/certificate"; | ||||||||||||||||||
import { User } from "@/types/bounty"; | ||||||||||||||||||
import { IRootState } from "@/store"; | ||||||||||||||||||
import Button from "@/components/ui/button"; | ||||||||||||||||||
import useIcpAuth, { IDENTITY_PROVIDER } from "@/hooks/useIcpAuth"; | ||||||||||||||||||
import { requestVerifiablePresentation, type VerifiablePresentationResponse } from "@dfinity/verifiable-credentials/request-verifiable-presentation"; | ||||||||||||||||||
import { Principal } from "@dfinity/principal"; | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider updating the dependency imports for consistency and maintenance. The imports for |
||||||||||||||||||
|
||||||||||||||||||
/** | ||||||||||||||||||
* interface for Achievement multiSelector | ||||||||||||||||||
|
@@ -45,6 +49,7 @@ const Achievement = () => { | |||||||||||||||||
const [showMintCertificate, setShowMintCertificate] = useState(false); | ||||||||||||||||||
const dispatch = useDispatch(); | ||||||||||||||||||
const { locale, query } = useRouter(); | ||||||||||||||||||
const { login } = useIcpAuth(); | ||||||||||||||||||
|
||||||||||||||||||
const findCertificateById = useCallback(async () => { | ||||||||||||||||||
await dispatch(findCertificate({ id: query.id as string })); | ||||||||||||||||||
|
@@ -104,6 +109,45 @@ const Achievement = () => { | |||||||||||||||||
return !achievement?.metadata?.image?.includes("/img/certificates/"); | ||||||||||||||||||
}, [achievement]); | ||||||||||||||||||
|
||||||||||||||||||
const requestVC = (principal: string) => { | ||||||||||||||||||
if (!achievement?.metadata) return; | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix unsafe optional chaining usage. The use of optional chaining with - if (!achievement?.metadata) return;
+ if (!achievement || !achievement.metadata) return; Committable suggestion
Suggested change
|
||||||||||||||||||
const { issuedOn, issuerName, linkToWork, recipientName, feedbacks, name } = achievement?.metadata; | ||||||||||||||||||
console.log({ issuedOn, issuerName, linkToWork, recipientName, feedbacks, name }); | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix unsafe optional chaining usage. The use of optional chaining with - if (!achievement?.metadata) return;
+ if (!achievement || !achievement.metadata) return; Committable suggestion
Suggested change
ToolsBiome
|
||||||||||||||||||
|
||||||||||||||||||
requestVerifiablePresentation({ | ||||||||||||||||||
onSuccess: async (verifiablePresentation: VerifiablePresentationResponse) => { | ||||||||||||||||||
console.log({ verifiablePresentation }); | ||||||||||||||||||
}, | ||||||||||||||||||
onError() { | ||||||||||||||||||
console.log("An error occurred"); | ||||||||||||||||||
}, | ||||||||||||||||||
issuerData: { | ||||||||||||||||||
/** | ||||||||||||||||||
* This issuer is for testing. | ||||||||||||||||||
* In this case dacade acts as a relying party requesting for verifiable identification | ||||||||||||||||||
* @link https://internetcomputer.org/docs/current/developer-docs/identity/verifiable-credentials/how-it-works#test-verifiable-credentials | ||||||||||||||||||
*/ | ||||||||||||||||||
origin: "https://qdiif-2iaaa-aaaap-ahjaq-cai.icp0.io", | ||||||||||||||||||
canisterId: Principal.fromText("qdiif-2iaaa-aaaap-ahjaq-cai"), | ||||||||||||||||||
}, | ||||||||||||||||||
credentialData: { | ||||||||||||||||||
credentialSpec: { | ||||||||||||||||||
credentialType: `Verified ${name} completion on Dacade`, | ||||||||||||||||||
arguments: { | ||||||||||||||||||
issuedOn, | ||||||||||||||||||
issuerName, | ||||||||||||||||||
linkToWork, | ||||||||||||||||||
recipientName, | ||||||||||||||||||
name, | ||||||||||||||||||
}, | ||||||||||||||||||
}, | ||||||||||||||||||
credentialSubject: Principal.fromText(principal), | ||||||||||||||||||
}, | ||||||||||||||||||
identityProvider: new URL(IDENTITY_PROVIDER), | ||||||||||||||||||
// derivationOrigin: window.location.href, | ||||||||||||||||||
}); | ||||||||||||||||||
}; | ||||||||||||||||||
|
||||||||||||||||||
return ( | ||||||||||||||||||
<> | ||||||||||||||||||
<Head> | ||||||||||||||||||
|
@@ -187,6 +231,7 @@ const Achievement = () => { | |||||||||||||||||
</AchievementViewItem> | ||||||||||||||||||
</div> | ||||||||||||||||||
)} | ||||||||||||||||||
<Button onClick={() => login((principal) => requestVC(principal))}>Generate VC</Button> | ||||||||||||||||||
</div> | ||||||||||||||||||
</div> | ||||||||||||||||||
</div> | ||||||||||||||||||
|
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.
Avoid using
any
type for global declarations.Using
any
forwindow.auth
andwindow.canister
can lead to runtime errors. Consider defining more specific types.