├── .gitignore ├── .env.template ├── create-authtoken.ts ├── revoke.ts ├── create-ten-minute-token.ts ├── package.json ├── LICENSE ├── revoke-all.ts ├── README.md └── lib.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- 1 | # Copy this file to a new one called .env and replace with your 12 or 24 word seed phrase 2 | SEED_PHRASE="boring something onions tiger marmalade six freedom discove halo marketplace social web" -------------------------------------------------------------------------------- /create-authtoken.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { createNewAuthToken } from "./lib"; 3 | 4 | if (!process.env.SEED_PHRASE) { 5 | throw new Error("missing seed phrase"); 6 | } 7 | 8 | async function main() { 9 | console.log("Creating an auth token that is long lived"); 10 | const token = await createNewAuthToken(9999999999999); 11 | 12 | console.log("Heres your token"); 13 | console.log(token); 14 | 15 | console.log( 16 | `To use it, add an "Authorization" header to your requests, with value "Bearer ${token.authenticationToken.secret}"` 17 | ); 18 | 19 | return token; 20 | } 21 | 22 | main(); 23 | -------------------------------------------------------------------------------- /revoke.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { revokeAuthToken } from "./lib"; 3 | 4 | const args = process.argv.slice(2); 5 | 6 | if (args.length !== 1) { 7 | throw new Error("Passed too few or too many arguments"); 8 | } 9 | 10 | // In the situation that you have 50 farcaster auth tokens and apps don't work because their auth token's expiry isn't long enough 11 | async function main() { 12 | console.log("Revoking specific token"); 13 | 14 | try { 15 | await revokeAuthToken(args[0]); 16 | console.log("Successfully revoked the auth token"); 17 | } catch (err) { 18 | console.log("Failed to revoke token"); 19 | } 20 | } 21 | 22 | main(); 23 | -------------------------------------------------------------------------------- /create-ten-minute-token.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { createNewAuthToken } from "./lib"; 3 | 4 | if (!process.env.SEED_PHRASE) { 5 | throw new Error("missing seed phrase"); 6 | } 7 | 8 | const TEN_MINUTES_IN_MILLIS = 600000; 9 | 10 | async function main() { 11 | const expiresInTenMins = Date.now() + TEN_MINUTES_IN_MILLIS; 12 | 13 | console.log("Creating an auth token that will expire in 10 minutes"); 14 | const token = await createNewAuthToken(expiresInTenMins); 15 | 16 | console.log("Heres your token"); 17 | console.log(token); 18 | 19 | console.log( 20 | `To use it, add an "Authorization" header to your requests, with value "Bearer ${token.authenticationToken.secret}"` 21 | ); 22 | 23 | return token; 24 | } 25 | 26 | main(); 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "revoke-all-auth-tokens", 3 | "version": "1.0.0", 4 | "description": "Some local scripts to make it easier for you to work with farcaster auth tokens.", 5 | "scripts": { 6 | "revoke": "ts-node revoke.ts", 7 | "revoke-all": "ts-node revoke-all.ts", 8 | "create-authtoken": "ts-node create-authtoken.ts", 9 | "create-ten-minute-token": "ts-node create-ten-minute-token.ts" 10 | }, 11 | "engines": { 12 | "node": "18" 13 | }, 14 | "author": "David Furlong", 15 | "license": "MIT", 16 | "dependencies": { 17 | "axios": "^1.2.2", 18 | "canonicalize": "^1.0.8", 19 | "dotenv": "^16.0.3", 20 | "ethers": "^5.7.2", 21 | "ts-node": "^10.9.1" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "^18.11.18" 25 | } 26 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 David Furlong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /revoke-all.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { createNewAuthToken, revokeAuthToken } from "./lib"; 3 | 4 | if (!process.env.SEED_PHRASE) { 5 | throw new Error("missing seed phrase"); 6 | } 7 | 8 | const NUM_TOKENS = 50; 9 | // In the situation that you have 50 farcaster auth tokens and apps don't work because their auth token's expiry isn't long enough 10 | async function main() { 11 | console.log("Revoking all your farcaster auth tokens"); 12 | // generate 50 new farcaster auth tokens with longest expiry, in order to invalidate all existing tokens 13 | const tokens = await Promise.all( 14 | Array.from(Array(NUM_TOKENS).keys()).map(() => 15 | // go for the a very big expiry to make sure they have longer expiry than all existing tokens 16 | createNewAuthToken(109999999999999) 17 | ) 18 | ); 19 | // revoke all of these auth tokens 20 | for (const token of tokens) { 21 | try { 22 | await revokeAuthToken(token.authenticationToken.secret); 23 | } catch (err) { 24 | console.log("Failed to revoke a token"); 25 | } 26 | } 27 | // now your account should now have 0 farcaster auth tokens 28 | 29 | console.log("Completed"); 30 | } 31 | 32 | main(); 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Warpcast.com API auth tokens for accessing Farcaster data 2 | 3 | Is a set of utility scripts for the warpcast.com API for Farcaster around generating and revoking auth tokens 4 | 5 | # Setup 6 | 7 | 1. Clone this repo 8 | 2. Set up your .env file using .env.template with your seed phrase from your farcaster wallet (12 or 24 words) 9 | 3. Make sure you have node 18 installed 10 | 4. Run `npm install` 11 | 5. Now you can run any of the scripts below 12 | 13 | # Scripts 14 | 15 | ## Revoke a single auth token 16 | 17 | Replace "MK-....==" with the auth token you want to revoke 18 | 19 | `npm run revoke MK-rDdfdstxfsdfFf0DhMwCmopf+NPYfw==` 20 | 21 | ## Revoke all auth tokens 22 | 23 | This will revoke all your farcaster auth tokens with expiresAt less than 109999999999999 24 | This is likely all your tokens, and will likely break any third party apps you have 25 | signed in with your farcaster wallet with. 26 | 27 | `npm run revoke-all` 28 | 29 | ## Create long lived auth token 30 | 31 | This token will live until it is revoked or until it is not one of the 50 tokens with the latest expiry date 32 | 33 | `npm run create-authtoken` 34 | 35 | ## Create an auth token that will be valid for 10 minutes 36 | 37 | `npm run create-ten-minute-token` 38 | 39 | # Errors 40 | 41 | Errors are unhandled, and will throw, exiting the process. The error will be logged to STDOUT 42 | -------------------------------------------------------------------------------- /lib.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import { ethers, utils } from "ethers"; 3 | import canonicalize from "canonicalize"; 4 | import axios from "axios"; 5 | 6 | export async function revokeAuthToken( 7 | authenticationToken: string 8 | ): Promise { 9 | await axios.delete("https://api.warpcast.com/v2/auth", { 10 | headers: { 11 | "Content-Type": "application/json", 12 | Authorization: `Bearer ${authenticationToken}`, 13 | }, 14 | data: { 15 | method: "revokeToken", 16 | params: { timestamp: Date.now() }, 17 | }, 18 | }); 19 | 20 | return true; 21 | } 22 | 23 | export async function createNewAuthToken(expiresAt: number) { 24 | const bearerToken = await generateFcBearerToken( 25 | process.env.SEED_PHRASE!, 26 | expiresAt 27 | ); 28 | const authenticationToken = await generateFcAuthenticationToken(bearerToken); 29 | 30 | const currentToken = { 31 | bearerToken, 32 | authenticationToken, 33 | }; 34 | 35 | return currentToken; 36 | } 37 | 38 | // https://farcasterxyz.notion.site/Authenticating-with-the-Merkle-V2-API-06226da351f447358a783b438b2aefdd 39 | async function generateFcBearerToken( 40 | mnemonic: string, 41 | expiresAt: number 42 | ): Promise<{ bearerToken: string; payload: string }> { 43 | const payload = canonicalize({ 44 | method: "generateToken", 45 | params: { 46 | timestamp: Date.now(), 47 | expiresAt: expiresAt, 48 | }, 49 | }) as string; 50 | 51 | // 2. The client’s custody address signs the canonicalized (RFC-8785) JSON message using EIP-191 to obtain a signature. 52 | const wallet = ethers.Wallet.fromMnemonic(mnemonic); 53 | const signature = Buffer.from( 54 | ethers.utils.arrayify(await wallet.signMessage(payload)) 55 | ).toString("base64"); 56 | 57 | const bearerToken = `eip191:${signature}`; 58 | 59 | const recoveredAddress = ethers.utils.recoverAddress( 60 | utils.hashMessage(payload), 61 | utils.hexlify(Buffer.from(bearerToken.split(":")[1], "base64")) 62 | ); 63 | 64 | if (recoveredAddress !== wallet.address) { 65 | console.error("ERROR invalid signature"); 66 | } 67 | 68 | return { bearerToken, payload }; 69 | } 70 | 71 | // baseline: https://replit.com/@VarunSrinivasa4/Merkle-V2-Custody-Bearer-Token-Example?v=1 72 | async function generateFcAuthenticationToken({ 73 | bearerToken, 74 | payload, 75 | }: { 76 | bearerToken: string; 77 | payload: string; 78 | }): Promise<{ secret: string; expiresAt: number }> { 79 | const res = await axios.put("https://api.warpcast.com/v2/auth", payload, { 80 | headers: { 81 | "Content-Type": "application/json", 82 | Authorization: `Bearer ${bearerToken}`, 83 | }, 84 | }); 85 | 86 | if (res.status >= 400) { 87 | console.error("Failed to create auth token", res.status); 88 | } 89 | 90 | const body: { 91 | result: { 92 | token: { 93 | secret: string; 94 | expiresAt: number; 95 | }; 96 | }; 97 | } = res.data; 98 | 99 | return body.result.token; 100 | } 101 | --------------------------------------------------------------------------------