diff --git a/.gitignore b/.gitignore index 81bbc2b..14ad881 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ temp/ # OS generated files .DS_Store Thumbs.db +TEMP_TODO.md diff --git a/html/index.html b/html/index.html index 443cbea..219f495 100644 --- a/html/index.html +++ b/html/index.html @@ -73,6 +73,7 @@ +
diff --git a/html/js/network/smirkAuth.js b/html/js/network/smirkAuth.js new file mode 100644 index 0000000..2ce8789 --- /dev/null +++ b/html/js/network/smirkAuth.js @@ -0,0 +1,298 @@ +/** + * SmirkAuth - Smirk Wallet Authentication Module + * + * Handles "Login with Smirk" wallet integration for Wownerogue. + * Uses the Smirk browser extension API (window.smirk) for wallet interactions. + * + * Flow: + * 1. Get challenge from server + * 2. Connect to Smirk extension to get public keys + * 3. Sign challenge with wallet + * 4. Verify signature with backend + * 5. Auto-set payout address from wallet + */ + +const SmirkAuth = { + /** + * Check if Smirk extension is installed + */ + isAvailable() { + return typeof window.smirk !== 'undefined'; + }, + + /** + * Current link status + */ + _isLinked: false, + _walletAddress: null, + + /** + * Main login flow + */ + async login() { + if (!this.isAvailable()) { + throw new Error('Smirk extension not installed. Please install it from your browser extension store.'); + } + + if (!window.socket || !window.socket.id) { + throw new Error('Not connected to server. Please wait for connection.'); + } + + const socketId = window.socket.id; + + // Step 1: Get challenge from server + const challengeRes = await fetch('/api/auth/smirk/challenge', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ socketId }) + }); + + if (!challengeRes.ok) { + const error = await challengeRes.json(); + throw new Error(error.message || 'Failed to get challenge from server'); + } + + const { challenge } = await challengeRes.json(); + + // Step 2: Connect to Smirk extension + let keys; + try { + keys = await window.smirk.connect(); + } catch (smirkError) { + throw new Error('Failed to connect to Smirk wallet: ' + (smirkError.message || 'Unknown error')); + } + + if (!keys || !keys.wow) { + throw new Error('Smirk wallet did not return a WOW address'); + } + + // Step 3: Sign the challenge + let signResult; + try { + signResult = await window.smirk.signMessage(challenge); + } catch (signError) { + throw new Error('Failed to sign challenge: ' + (signError.message || 'Unknown error')); + } + + // Find WOW signature + const wowSig = signResult.signatures?.find(s => s.asset === 'wow'); + if (!wowSig) { + throw new Error('WOW signature not found in Smirk response'); + } + + // Step 4: Verify signature with backend + const verifyRes = await fetch('/api/auth/smirk/verify', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + socketId, + challenge, + publicKey: wowSig.publicKey, + signature: wowSig.signature + }) + }); + + if (!verifyRes.ok) { + const error = await verifyRes.json(); + throw new Error(error.message || 'Signature verification failed'); + } + + const verifyData = await verifyRes.json(); + + // Step 5: Auto-set payout address from wallet + if (keys.wow && window.socket) { + window.socket.emit('address:update', { address: keys.wow }); + } + + // Update internal state + this._isLinked = true; + this._walletAddress = keys.wow; + + return { + success: true, + linked: verifyData.linked, + address: keys.wow + }; + }, + + /** + * Check current link status + */ + async checkStatus() { + if (!window.socket || !window.socket.id) { + return { linked: false, hasPayoutAddress: false }; + } + + try { + const res = await fetch(`/api/auth/smirk/status?socketId=${encodeURIComponent(window.socket.id)}`); + if (res.ok) { + const data = await res.json(); + this._isLinked = data.linked; + return data; + } + } catch (e) { + console.warn('Failed to check Smirk status:', e.message); + } + + return { linked: false, hasPayoutAddress: false }; + }, + + /** + * Create and return the Smirk login button element + */ + _createButton() { + const btn = $('