export async function GetBlueskyDID(PDS, Handle) { let request = fetch(PDS + "/xrpc/com.atproto.identity.resolveDid?handle=" + Handle, { method: "GET"}) .then((response) => response.json()); return request; } // Component 1/4 export async function GetPDSWellKnown() { let Data = await fetch("https://bsky.social/.well-known/oauth-authorization-server", {method: "GET"}) .then((response) => response.json()); return Data; } // Component 2/4 // Many thanks to https://github.com/tonyxu-io/pkce-generator. It was the base for this code. export async function CreatePKCECodeVerifier() { // Generate some Numbers let Numbers = new Uint8Array(32); crypto.getRandomValues(Numbers); // Generate a random string of characters. let CodeVerifier = ""; for (let i in Numbers) { CodeVerifier += String.fromCharCode(Numbers[i]); } // Put this random string into Base64URL format. CodeVerifier = btoa(CodeVerifier).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); return CodeVerifier; } export async function CreatePKCECodeChallenge(CodeVerifier) { // Generate a code challenge with the code verifier. // This is done by first SHA256 encrypting the CodeVerifier, then putting the outputted string into Base64URL format. let CodeChallenge = btoa(await sha256(CodeVerifier)).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); return CodeChallenge; } // Component 3/4 export async function PARrequest(PAREndpoint, State, ChallengeCode) { return fetch(PAREndpoint, {method: "POST", body: new URLSearchParams({ response_type: "code", code_challenge_method: "S256", scope: "atproto transition:generic", client_id: "https://fedi.crowdedgames.group/oauth/client-metadata.json", redirect_uri: "https://fedi.crowdedgames.group/HTML/setting.html", code_challenge: ChallengeCode, state: State, login_hint: "crowdedgames.group" }), headers: {"Content-Type": "application/x-www-form-urlencoded"}}); } export async function AuthRequest(TokenEndpoint, ChallengeVerifier, code, DPoP) { return fetch(TokenEndpoint, {method: "POST", body: new URLSearchParams({ grant_type: "authorization_code", code: code, client_id: "https://fedi.crowdedgames.group/oauth/client-metadata.json", redirect_uri: "https://fedi.crowdedgames.group/HTML/setting.html", code_verifier: ChallengeVerifier}), headers: { "DPoP": DPoP, "Content-Type": "application/x-www-form-urlencoded"}}); } // Component 4/4 export async function ClientDPoP(POSTorGET, RequestURL, DPoPNonce) { let KeyPair = await crypto.subtle.generateKey({name: "ECDSA", namedCurve: "P-256"}, true, ["sign", "verify"]); // Header var Header = {alg: "ES256", typ: "dpop+jwt", jwk: await crypto.subtle.exportKey("jwk", KeyPair.publicKey).then(function(response) {return response})}; // Payload var Payload = {}; Payload.jti = GenerateToken(64); Payload.htm = POSTorGET; Payload.htu = RequestURL; Payload.iat = Math.floor(new Date(Date.now()).getTime() / 1000); Payload.iss = "https://fedi.crowdedgames.group/oauth/client-metadata.json"; Payload.nonce = DPoPNonce; var sHeader = JSON.stringify(Header); var sPayload = JSON.stringify(Payload); var JWT = KJUR.jws.JWS.sign("ES256", sHeader, sPayload, await crypto.subtle.exportKey("jwk", KeyPair.privateKey).then(function(response) {return response})); console.log(JWT); return JWT; } // So far does nothing? Don't touch :3 export async function AssertionJWT() { let KeyPair = await crypto.subtle.generateKey({name: "ECDSA", namedCurve: "P-256"}, true, ["sign", "verify"]); // Header var Header = {alg: "HS256", kid: await crypto.subtle.exportKey("jwk", KeyPair.publicKey).then(function(response) {return response})}; // Payload var Payload = {}; Payload.iss = "https://fedi.crowdedgames.group/oauth/client-metadata.json"; Payload.sub = "https://fedi.crowdedgames.group/oauth/client-metadata.json"; // Payload.aud Payload.jti = GenerateToken(64); Payload.iat = Math.floor(new Date(Date.now()).getTime() / 1000); var sHeader = JSON.stringify(Header); var sPayload = JSON.stringify(Payload); var JWT = KJUR.jws.JWS.sign("HS256", sHeader, sPayload, "838383"); } export async function HandleAuthorization(BlueskyPKCEverifer, BlueskyPKCEchallenge, BlueskyNonce) { // Declare Variables let KeyPair = await crypto.subtle.generateKey({name: "ECDSA", namedCurve: "P-256"}, true, ["sign", "verify"]); let WellKnown = await GetPDSWellKnown(); let PAREndpoint = WellKnown.pushed_authorization_request_endpoint; let State = GenerateToken(64); let PKCEverifier = await CreatePKCECodeVerifier(); let PKCEchallenge = await CreatePKCECodeChallenge(PKCEverifier); // PAR request (beginning) let PAR = PARrequest(WellKnown.pushed_authorization_request_endpoint, State, PKCEchallenge); let body = await PAR.then((response) => response.json()); let nonce = await PAR.then((response) => response.headers.get("dpop-nonce")); let ExportedKey1 = await crypto.subtle.exportKey("raw", KeyPair.publicKey); // Save these things. document.cookie = BlueskyPKCEverifer + "=" + PKCEverifier + ";samesite=strict;path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT;"; document.cookie = BlueskyPKCEchallenge + "=" + PKCEchallenge + ";samesite=strict;path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT;"; document.cookie = BlueskyNonce + "=" + nonce + ";samesite=strict;path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT;"; // Don't remove the keys. They are important. // if (document.cookie.split("; ").find((row) => row.startsWith(BlueskyPublicKey + "="))?.split("=").length == 1 || document.cookie.split("; ").find((row) => row.startsWith(BlueskyPrivateKey + "="))?.split("=").length == 1) { // document.cookie = BlueskyPublicKey + "=" + ExportedKey1 + ";samesite=strict;path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT;"; // } // Now we need to authenticate. Make sure the State stays the same throughout this whole process :] document.location.href = "https://bsky.social/oauth/authorize?client_id=https://fedi.crowdedgames.group/oauth/client-metadata.json&request_uri=" + body.request_uri; } export async function GainTokens(PKCEcodeName, NonceName) { if ((document.location.href.split("state=").length > 1 && document.location.href.split("iss=").length > 1 && document.location.href.split("code=").length > 1) && document.cookie.split("; ").find((row) => row.startsWith(PKCEcodeName + "="))?.split("=").length > 1) { let DPoP = ClientDPoP("POST", "https://bsky.social/oauth/token", document.cookie.split("; ").find((row) => row.startsWith(NonceName + "="))?.split("=")[1]); let PKCE = document.cookie.split("; ").find((row) => row.startsWith(PKCEcodeName + "="))?.split("=")[1]; let code = document.location.href.split("code=")[1]; console.log(code); let Auth = AuthRequest("https://bsky.social/oauth/token", PKCE, code, DPoP); console.log(AuthRequest); } } // Stolen from elsewhere. // Firefox snippet; Slightly edited. async function sha256(message) { // encode as UTF-8 const MessageBuffer = new TextEncoder().encode(message); // hash the message const HashBuffer = await crypto.subtle.digest('SHA-256', MessageBuffer); // convert ArrayBuffer to Array const HashArray = Array.from(new Uint8Array(HashBuffer)); // convert this hashArray to a string let string = ""; for (let i in HashArray) { string += String.fromCharCode(HashArray[i]); } return string; } // Stolen from Search // TODO: implement my own function. export function GenerateToken(length) { var chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; var token = ''; for(var i = 0; i < length; i++) { token += chars[Math.floor(Math.random() * chars.length)]; } return token; }