UNPKG

ic-auth

Version:

A simple, modular package for integrating Internet Computer authentication providers into your app.

245 lines (231 loc) 8.92 kB
import { Principal } from '@dfinity/principal'; import { Identity, HttpAgent, Agent, Actor } from '@dfinity/agent'; import { AuthClient } from '@dfinity/auth-client'; import { InterfaceFactory } from '@dfinity/candid/lib/cjs/idl'; //@ts-ignore import { StoicIdentity } from 'ic-stoic-identity'; import { ActorSubclass, ActorMethod } from '@dfinity/agent'; // Types /** * Represents an authenticated user session. */ export type UserObject = { principal: string, agent: Agent | undefined, provider: string } // Helpers const shouldFetchRootKey = (host: string): boolean => { return !(host.includes('ic0.app') || host.includes('icp0.io')); }; // A simple Stoic login flow with simplified return data. // Todo: Stoic is giving a cookies error, need to wait for a response from the stoic team. /** * Authenticate the user with the Stoic Wallet provider and return a connected UserObject. * * This flow checks if Stoic is already connected, otherwise prompts the user to connect. * If successful, it creates a new Agent tied to the identity and returns the principal and agent. * * @param host - The IC replica host to connect to (e.g., local or mainnet). * @returns A Promise resolving to a UserObject containing principal, agent, and provider info. * @throws If Stoic identity cannot be loaded or connection fails. * * @todo Stoic is currently known to throw a cookie-related error in some browsers. * This may be fixed in a later version. */ export const StoicLogin = async(host: string) : Promise<UserObject> => { // Checks to see if stoic is already connected or not, then prompts. let identity: Identity | null = await StoicIdentity.load(); if (!identity) { identity = await StoicIdentity.connect(); } if (!identity) { throw new Error("Stoic Identity not found or connection failed."); } const agent = await HttpAgent.create({ identity: identity, host: host, shouldFetchRootKey: shouldFetchRootKey(host) }) const principal = identity.getPrincipal().toText(); const returnObject : UserObject = { principal: principal, agent: agent, provider: "Stoic" } console.log("Connected!"); console.log(returnObject); return returnObject; } // Full featured Plug wallet login with simplified return data. /** * Logs in with Plug Wallet and returns the authenticated user's agent and information. * * @param whitelist - An array of canister IDs the app will access using Plug Wallet. * @param host - The URL of the Internet Computer replica to connect to. * Use "https://icp0.io" or "https://ic0.app" for mainnet or "http://127.0.0.1:<YourDFXPort>" for local development. * @returns A Promise that resolves to a UserObject containing the agent, principal, and provider name. */ export const PlugLogin = async(whitelist: string[], host: string) : Promise<UserObject> => { const isConnected = await (window as any).ic.plug.isConnected() as boolean; if (isConnected) { await (window as any).ic.plug.createAgent({whitelist, host}); const agent = (window as any).ic.plug.agent as Agent; const principal = (await agent.getPrincipal()).toText(); const returnObject : UserObject = { principal: principal, agent: agent, provider: "Plug" } console.log("Connected!"); console.log(returnObject); return returnObject; } else { await (window as any).ic.plug.requestConnect({whitelist, host}); const agent = (window as any).ic.plug.agent as Agent; const principal = (await agent.getPrincipal()).toText(); const returnObject : UserObject = { principal: principal, agent: agent, provider: "Plug" } console.log("Connected!"); console.log(returnObject); return returnObject; } } // A full featured NFID login flow with modern agent and simplified return data. /** * Authenticate the user using NFID and return a connected UserObject. * * This flow uses DFINITY's AuthClient with NFID as the identity provider. * It constructs the NFID authorization URL with application name and logo, * creates a modern Agent tied to the authenticated identity, and resolves the user details. * * @param host - The IC replica host to connect to (mainnet or local). * @returns A Promise resolving to a UserObject containing principal, agent, and provider info. * @throws If creating the AuthClient or login flow fails. */ export const NFIDLogin = async (host: string): Promise<UserObject> => { return new Promise<UserObject>(async (resolve, reject) => { const appName = "wallet-testing"; const appLogo = "https://nfid.one/icons/favicon-96x96.png"; const authUrl = `https://nfid.one/authenticate/?applicationName=${appName}&applicationLogo=${appLogo}#authorize`; let userObject: UserObject = { principal: "Not Connected.", agent: undefined, provider: "N/A", }; try { const authClient = await AuthClient.create(); await authClient.login({ identityProvider: authUrl, onSuccess: async () => { const identity = authClient.getIdentity(); const agent = await HttpAgent.create({ identity, host, shouldFetchRootKey: shouldFetchRootKey(host), }); userObject = { principal: Principal.from(identity.getPrincipal()).toText(), agent, provider: "NFID", }; console.log("Connected!"); console.log(userObject); resolve(userObject); }, onError: (error) => { console.error("NFID Login Failed:", error); reject(error); }, }); } catch (error) { console.error("Error creating AuthClient:", error); reject(error); } }); }; // A fully featured Internet Identity login flow with simplified return data. /** * Authenticate the user using Internet Identity and return a connected UserObject. * * This flow uses DFINITY's AuthClient to open the Internet Identity login, * creates an Agent with the returned identity, and resolves the user details. * It handles both mainnet and local environments by conditionally fetching the root key. * * @param host - The IC replica host to connect to (mainnet or local). * @returns A Promise resolving to a UserObject containing principal, agent, and provider info. * @throws If creating the AuthClient or login flow fails. */ export const IdentityLogin = async (host: string): Promise<UserObject> => { return new Promise<UserObject>(async (resolve, reject) => { let identity: Identity; let userObject: UserObject = { principal: "Not Connected.", agent: undefined, provider: "N/A", }; try { const authClient = await AuthClient.create(); await authClient.login({ identityProvider: "https://identity.ic0.app", onSuccess: async () => { identity = authClient.getIdentity(); const agent = await HttpAgent.create({ identity: identity, host: host, shouldFetchRootKey: shouldFetchRootKey(host), }); userObject = { principal: Principal.from(identity.getPrincipal()).toText(), agent: agent, provider: "Internet Identity", }; console.log("Connected!"); console.log(userObject); resolve(userObject); }, onError: async (error: any) => { userObject = { principal: "Not Connected.", agent: undefined, provider: "N/A", }; console.log("Error Logging In"); reject(error); }, }); } catch (error) { console.error("Error creating AuthClient:", error); reject(error); } }); }; /** * Create an actor for any canister using a provided agent. * @param agent - The authenticated or anonymous Agent instance. * @param idl - The IDL factory for the target canister. * @param canisterId - The canister ID to connect to. * @returns A typed Actor subclass for the canister. */ export const CreateActor = async (agent: Agent, idl: InterfaceFactory, canisterId: string): Promise<ActorSubclass<Record<string, ActorMethod<unknown[], unknown>>>> => { const actor = Actor.createActor(idl, { agent, canisterId, }); return actor; }; /** * Create an anonymous agent for public queries or read-only calls. * @param host - Optional IC replica host (defaults to mainnet). * @returns An Agent instance with no identity attached. */ export const CreateAnonAgent = async (host: string): Promise<Agent> => { const agent = await HttpAgent.create({ host, shouldFetchRootKey: shouldFetchRootKey(host), }); return agent; };