From 2a20fd544e301e69b18650a379ce0be0f1d4aed5 Mon Sep 17 00:00:00 2001 From: CatAClock Date: Wed, 23 Apr 2025 16:02:44 -0700 Subject: [PATCH 01/16] Made it more explicit that I am not using ActivityPub --- JS/ActivityPub.js | 20 --------- JS/BlueskyAPI.js | 0 JS/MastodonAPI.js | 101 ++++++++++++++++++++++++++++++++++++++++++ JS/TumblrAPI.js | 0 JS/index.js | 8 ++-- JS/mail.js | 110 ++++++++++++++++------------------------------ JS/setting.js | 4 +- 7 files changed, 145 insertions(+), 98 deletions(-) delete mode 100644 JS/ActivityPub.js create mode 100644 JS/BlueskyAPI.js create mode 100644 JS/MastodonAPI.js create mode 100644 JS/TumblrAPI.js diff --git a/JS/ActivityPub.js b/JS/ActivityPub.js deleted file mode 100644 index c8d2c11..0000000 --- a/JS/ActivityPub.js +++ /dev/null @@ -1,20 +0,0 @@ -export const Scopes = "read write follow push"; - -export async function GetPublicTimeline(Local = false, Remote = false) { - let Timeline; - if (Local == true && Remote == true) { - console.error("Don't set both Local and Remote timelines to true."); - } - - if (Local == true) { - Timeline = await fetch("https://wetdry.world/api/v1/timelines/public?limit=12&local=true") - .then((response) => response.json()); - } else if (Remote == true) { - Timeline = await fetch("https://wetdry.world/api/v1/timelines/public?limit=12&remote=true") - .then((response) => response.json()); - } else { - Timeline = await fetch("https://wetdry.world/api/v1/timelines/public?limit=12") - .then((response) => response.json()); - } - return Timeline; -} diff --git a/JS/BlueskyAPI.js b/JS/BlueskyAPI.js new file mode 100644 index 0000000..e69de29 diff --git a/JS/MastodonAPI.js b/JS/MastodonAPI.js new file mode 100644 index 0000000..c5b56ca --- /dev/null +++ b/JS/MastodonAPI.js @@ -0,0 +1,101 @@ +export const Scopes = "read write follow push"; + +// Gets the public timeline. +export async function GetPublicTimeline(Local = false, Remote = false, Website) { + let Timeline; + if (Local == true && Remote == true) { + console.error("Don't set both Local and Remote timelines to true."); + return Timeline; + } + + if (Local == true) { + Timeline = await fetch(Website + "/api/v1/timelines/public?limit=12&local=true") + .then((response) => response.json()); + } else if (Remote == true) { + Timeline = await fetch(Website + "/api/v1/timelines/public?limit=12&remote=true") + .then((response) => response.json()); + } else { + Timeline = await fetch(Website + "/api/v1/timelines/public?limit=12") + .then((response) => response.json()); + } + return Timeline; +} + +// Gets the favorites of a user that you have access to. +export async function GetFavorites(Website, MastodonAccessToken, MastodonTokenType) { + let Favorites; + // Check for a token. + if (document.cookie.split("; ").find((row) => row.startsWith(MastodonAccessToken + "="))?.split("=").length > 1) { + // Get the varaibles that are stored in cookies. + let AccessToken = document.cookie.split("; ").find((row) => row.startsWith(MastodonAccessToken + "="))?.split("=")[1]; + let TokenType = document.cookie.split("; ").find((row) => row.startsWith(MastodonTokenType + "="))?.split("=")[1]; + Favorites = await fetch(Website + "/api/v1/favourites", {method: "GET", headers: {"Authorization": TokenType + " " + AccessToken}}) + .then((response) => response.json()); + } + return Favorites; +} + +// Gets the bookmarks of a user that you have access to. +export async function GetBookmarks(Website, MastodonAccessToken, MastodonTokenType) { + let Bookmarks; + // Check for a token. + if (document.cookie.split("; ").find((row) => row.startsWith(MastodonAccessToken + "="))?.split("=").length > 1) { + // Get the varaibles that are stored in cookies. + let AccessToken = document.cookie.split("; ").find((row) => row.startsWith(MastodonAccessToken + "="))?.split("=")[1]; + let TokenType = document.cookie.split("; ").find((row) => row.startsWith(MastodonTokenType + "="))?.split("=")[1]; + Bookmarks = await fetch(Website + "/api/v1/bookmarks", {method: "GET", headers: {"Authorization": TokenType + " " + AccessToken}}) + .then((response) => response.json()); + } + return Bookmarks; +} + +// Gets the notifications of a user that you have access to. +export async function GetNotifications(Website, MastodonAccessToken, MastodonTokenType) { + let Notifications; + // Check for a token. + if (document.cookie.split("; ").find((row) => row.startsWith(MastodonAccessToken + "="))?.split("=").length > 1) { + // Get the varaibles that are stored in cookies. + let AccessToken = document.cookie.split("; ").find((row) => row.startsWith(MastodonAccessToken + "="))?.split("=")[1]; + let TokenType = document.cookie.split("; ").find((row) => row.startsWith(MastodonTokenType + "="))?.split("=")[1]; + Notifications = await fetch(Website + "/api/v1/notifications", {method: "GET", headers: {"Authorization": TokenType + " " + AccessToken}}) + .then((response) => response.json()); + } + return Notifications; +} + +// The first step to using the app. +export async function HandleAuthentication(Website, CookieClientID, CookieClientSecret) { + // See if the user is smart enough to put https. + let InstanceData; + if (!Website.toLowerCase().split("://")[0].includes("https")) { + // Quickly check to see if it has something before :// so it doesn't screw the link. + if (Website.toLowerCase().split("://").length > 1) { + Website = "https://" + Website.split("://")[1]; + } else { + Website = "https://" + Website; + } + InstanceData = await fetch(Website + "/api/v1/apps?client_name=Channel Viewer&redirect_uris=" + Origin + "&scopes=" + ActivityPub.Scopes, {method: "POST"}) + .then((response) => response.json()); + } + // Save the client stuff as cookies. + document.cookie = CookieClientID + "=" + InstanceData.client_id + ";samesite=strict;path=/;expires=9999-01-01;"; + document.cookie = CookieClientSecret + "=" + InstanceData.client_secret + ";samesite=strict;path=/;expires=9999-01-01;"; + // Now authenticate the app. + document.location.href = Website + "/oauth/authorize?client_id=" + InstanceData.client_id + "&redirect_uri=" + Origin + "&response_type=code&scope=" + ActivityPub.Scopes; +} + +// This specific functino goes after HandleAuthentication for when login happens. +export async function GainToken(Website, CookieClientID, CookieClientSecret, CookieAccessToken, CookieTokenType) { + // check if you both have a code and have a current authentication. + if (document.location.href.split("code=").length > 1 && document.cookie.split("; ").find((row) => row.startsWith(CookieClientID + "="))?.split("=") > 1) { + let code = document.location.href.split("code=")[1]; + let ClientID = document.cookie.split("; ").find((row) => row.startsWith(CookieClientID + "="))?.split("=")[1]; + let ClientSecret = document.cookie.split("; ").find((row) => row.startsWith(CookieClientSecret + "="))?.split("=")[1]; + + let AuthenticationToken = await fetch(Website + "/oauth/token?client_id=" + ClientID + "&client_secret=" + ClientSecret + "&redirect_uri=" + Origin + "&grant_type=authorization_code&code=" + code, {method: "POST"}) + .then((response) => response.json()); + // Cookify These + document.cookie = CookieAccessToken + "=" + AuthenticationToken.access_token + ";samesite=strict;path=/;expires=9999-01-01;"; + document.cookie = CookieTokenType + "=" + AuthenticationToken.token_type + ";samesite=strict;path=/;expires=9999-01-01;"; + } +} diff --git a/JS/TumblrAPI.js b/JS/TumblrAPI.js new file mode 100644 index 0000000..e69de29 diff --git a/JS/index.js b/JS/index.js index 295bcf5..4b26928 100644 --- a/JS/index.js +++ b/JS/index.js @@ -1,4 +1,4 @@ -import * as ActivityPub from "./ActivityPub.js"; +import * as MastodonAPI from "./MastodonAPI.js"; // GLOBAL VARS // fuck you. I see why website developers use divs so fucking often. @@ -18,6 +18,8 @@ const ButtonSound = new Audio("Audio/button-305770.mp3"); const WarningClick = new Audio("Audio/button-pressed-38129.mp3"); const BackgroundMusic = new Audio("Audio/soft-piano-music-312509.mp3"); +// Websites + // Update a timer function UpdateTime() { let TimeNow = new Date(); @@ -98,7 +100,7 @@ ArrowsButton[0].onclick = (event) => { PosterContainerUpdate(); } -// ActivityPub integration +// MastodonAPI integration async function PosterContainerUpdate() { // Cookies for the public timelines let LocalCookie = document.cookie.split("; ").find((row) => row.startsWith("Local="))?.split("=")[1]; @@ -106,7 +108,7 @@ async function PosterContainerUpdate() { let RemoteCookie = document.cookie.split("; ").find((row) => row.startsWith("Remote="))?.split("=")[1]; var RemoteTrue = (RemoteCookie === 'true'); - let Timeline = await ActivityPub.GetPublicTimeline(LocalTrue, RemoteTrue); + let Timeline = await ActivityPub.GetPublicTimeline(LocalTrue, RemoteTrue, MastodonWebsite); let Content = []; let Users = []; for (let i in Timeline) { diff --git a/JS/mail.js b/JS/mail.js index dceda34..f5f438a 100644 --- a/JS/mail.js +++ b/JS/mail.js @@ -1,47 +1,30 @@ -import * as ActivityPub from "./ActivityPub.js"; +import * as MastodonAPI from "./MastodonAPI.js"; let LoginButton = document.getElementsByClassName("Login")[0]; let LogoutButton = document.getElementsByClassName("Logout")[0]; let Origin = window.location.origin + "/HTML/mail" +// Mastodon +let MastodonWebsite = "https://wetdry.world"; +let MastodonClientID = "mastodon_client_id"; +let MastodonClientSecret = "mastodon_client_secret"; +let MastodonAccessToken = "mastodon_access_token"; +let MastodonTokenType = "mastodon_access_token"; + LoginButton.onclick = (event) => { - HandleAuthentication(); + MastodonAPI.HandleAuthentication(MastodonWebsite, MastodonClientID, MastodonClientSecret); } LogoutButton.onclick = (event) => { - console.log("Does nothing so far."); -} - -async function HandleAuthentication() { - let InstanceData = await fetch("https://wetdry.world/api/v1/apps?client_name=Channel Viewer&redirect_uris=" + Origin + "&scopes=" + ActivityPub.Scopes, {method: "POST"}) - .then((response) => response.json()); - // Save the client stuff as cookies (STRICTLY THIS SITE!). - document.cookie = "client_id=" + InstanceData.client_id + ";samesite=strict;path=/;expires=Thu, 01 Jan 9999 00:00:00 GMT;"; - document.cookie = "client_secret=" + InstanceData.client_secret + ";samesite=strict;path=/;expires=Thu, 01 Jan 9999 00:00:00 GMT;"; - // Now authenticate the app. - let InstanceInfo = await fetch("https://wetdry.world/api/v1/instance", {method: "GET"}) - .then((response) => response.json()); - document.location.href = "https://wetdry.world/oauth/authorize?client_id=" + InstanceData.client_id + "&redirect_uri=" + Origin + "&response_type=code&scope=" + ActivityPub.Scopes; -} - -// When the website starts, check to see if you actually went and got a code. -async function HandleLogin() { - if (document.location.href.split("code=").length > 1 && document.cookie.split("; ").find((row) => row.startsWith("access_token="))?.split("=") > 1) { - let code = document.location.href.split("code=")[1]; - let ClientID = document.cookie.split("; ").find((row) => row.startsWith("client_id="))?.split("=")[1]; - let ClientSecret = document.cookie.split("; ").find((row) => row.startsWith("client_secret="))?.split("=")[1]; - - let AuthenticationToken = await fetch("https://wetdry.world/oauth/token?client_id=" + ClientID + "&client_secret=" + ClientSecret + "&redirect_uri=" + Origin + "&grant_type=authorization_code&code=" + code, {method: "POST"}) - .then((response) => response.json()); - // Cookify These - document.cookie = "access_token=" + AuthenticationToken.access_token + ";samesite=strict;path=/;expires=Thu, 01 Jan 9999 00:00:00 GMT;"; - document.cookie = "token_type=" + AuthenticationToken.token_type + ";samesite=strict;path=/;expires=Thu, 01 Jan 9999 00:00:00 GMT;"; - } + document.cookie = MastodonAccessToken + "=nothing;" + ";samesite=strict;path=/;expires=0000-01-01;"; + document.cookie = MastodonTokenType + "=nothing;" + ";samesite=strict;path=/;expires=0000-01-01;"; + console.log("Cleared the access token."); } +// if an access token is found, login. function CheckLogin() { // Check for a token. - if (document.cookie.split("; ").find((row) => row.startsWith("access_token="))?.split("=").length > 1) { + if (document.cookie.split("; ").find((row) => row.startsWith(MastodonAccessToken + "="))?.split("=").length > 1) { // Swap the buttons LoginButton.remove(); LogoutButton.setAttribute("style", ""); @@ -50,54 +33,35 @@ function CheckLogin() { // Below is the thing it populates if you login. async function PopulateFavorites() { - // Check for a token. - if (document.cookie.split("; ").find((row) => row.startsWith("access_token="))?.split("=").length > 1) { - // Get the varaibles that are stored in cookies. - let AccessToken = document.cookie.split("; ").find((row) => row.startsWith("access_token="))?.split("=")[1]; - let TokenType = document.cookie.split("; ").find((row) => row.startsWith("token_type="))?.split("=")[1]; - let Favorites = await fetch("https://wetdry.world/api/v1/favourites", {method: "GET", headers: {"Authorization": TokenType + " " + AccessToken}}) - .then((response) => response.json()); - let FavoritesArea = document.getElementsByClassName("Favorites")[0]; - // Populate the favorites area. - for (let i in Favorites) { - FavoritesArea.innerHTML += "
"; - FavoritesArea.getElementsByClassName("Favorite")[i].innerHTML = Favorites[i].content; - } + let Favorites = MastodonAPI.GetFavorites(MastodonWebsite, MastodonAccessToken, MastodonTokenType); + + let FavoritesArea = document.getElementsByClassName("Favorites")[0]; + // Populate the favorites area. + for (let i in Favorites) { + FavoritesArea.innerHTML += "
"; + FavoritesArea.getElementsByClassName("Favorite")[i].innerHTML = Favorites[i].content; } } async function PopulateBookmarks() { - // Check for a token. - if (document.cookie.split("; ").find((row) => row.startsWith("access_token="))?.split("=").length > 1) { - // Get the varaibles that are stored in cookies. - let AccessToken = document.cookie.split("; ").find((row) => row.startsWith("access_token="))?.split("=")[1]; - let TokenType = document.cookie.split("; ").find((row) => row.startsWith("token_type="))?.split("=")[1]; - let Bookmarks = await fetch("https://wetdry.world/api/v1/bookmarks", {method: "GET", headers: {"Authorization": TokenType + " " + AccessToken}}) - .then((response) => response.json()); - let BookmarksArea = document.getElementsByClassName("Bookmarks")[0]; - // Populate the Bookmarks area. - for (let i in Bookmarks) { - BookmarksArea.innerHTML += "
"; - BookmarksArea.getElementsByClassName("Bookmark")[i].innerHTML = Bookmarks[i].content; - } + let Bookmarks = MastodonAPI.GetBookmarks(MastodonWebsite, MastodonAccessToken, MastodonTokenType); + + let BookmarksArea = document.getElementsByClassName("Bookmarks")[0]; + // Populate the Bookmarks area. + for (let i in Bookmarks) { + BookmarksArea.innerHTML += "
"; + BookmarksArea.getElementsByClassName("Bookmark")[i].innerHTML = Bookmarks[i].content; } } async function PopulateNotifications() { - // Check for a token. - if (document.cookie.split("; ").find((row) => row.startsWith("access_token="))?.split("=").length > 1) { - // Get the varaibles that are stored in cookies. - let AccessToken = document.cookie.split("; ").find((row) => row.startsWith("access_token="))?.split("=")[1]; - let TokenType = document.cookie.split("; ").find((row) => row.startsWith("token_type="))?.split("=")[1]; - let Notifications = await fetch("https://wetdry.world/api/v1/notifications", {method: "GET", headers: {"Authorization": TokenType + " " + AccessToken}}) - .then((response) => response.json()); - console.log(Notifications); - let NotificationsArea = document.getElementsByClassName("Notifications")[0]; - // Populate the Conversations area. - for (let i in Notifications) { - NotificationsArea.innerHTML += "
"; - NotificationsArea.getElementsByClassName("Notification")[i].innerHTML = Notifications[i].status.content; - } + let Notifications = MastodonAPI.GetNotifications(MastodonWebsite, MastodonAccessToken, MastodonTokenType); + + let NotificationsArea = document.getElementsByClassName("Notifications")[0]; + // Populate the Conversations area. + for (let i in Notifications) { + NotificationsArea.innerHTML += "
"; + NotificationsArea.getElementsByClassName("Notification")[i].innerHTML = Notifications[i].status.content; } } @@ -105,8 +69,8 @@ async function PopulateNotifications() { // Remove traces of "login". CheckLogin(); -// Authentication. -HandleLogin(); +// So far: login. TODO: Change this later. +MastodonAPI.GainToken(MastodonWebsite, MastodonClientID, MastodonClientSecret, MastodonAccessToken, MastodonTokenType); // Populate the areas. PopulateFavorites(); diff --git a/JS/setting.js b/JS/setting.js index e658806..e23c534 100644 --- a/JS/setting.js +++ b/JS/setting.js @@ -4,7 +4,7 @@ let RemoteButton = document.getElementsByClassName("Remote")[0]; LocalButton.onclick = (event) => { // Toggle the cookie if (document.cookie.split(";").some((item) => item.trim().startsWith("Local="))) { - document.cookie = "Local=true;samesite=strict;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;"; + document.cookie = "Local=true;samesite=strict;path=/;expires=expires=0000-01-01;"; } else { document.cookie = "Local=true;samesite=strict;path=/;"; } @@ -13,7 +13,7 @@ LocalButton.onclick = (event) => { RemoteButton.onclick = (event) => { // Toggle the cookie if (document.cookie.split(";").some((item) => item.trim().startsWith("Remote="))) { - document.cookie = "Remote=true;samesite=strict;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;"; + document.cookie = "Remote=true;samesite=strict;path=/;expires=expires=0000-01-01;"; } else { document.cookie = "Remote=true;samesite=strict;path=/;"; } -- 2.45.3 From 723f4c15d763d5e1a7608d9188803744354e7fd3 Mon Sep 17 00:00:00 2001 From: CatAClock Date: Wed, 23 Apr 2025 16:18:42 -0700 Subject: [PATCH 02/16] learning --- JS/mail.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/JS/mail.js b/JS/mail.js index f5f438a..352cc36 100644 --- a/JS/mail.js +++ b/JS/mail.js @@ -1,4 +1,6 @@ import * as MastodonAPI from "./MastodonAPI.js"; +import * as BlueskyAPI from "./BlueskyAPI.js"; +import * as TumblrAPI from "./TumblrAPI.js"; let LoginButton = document.getElementsByClassName("Login")[0]; let LogoutButton = document.getElementsByClassName("Logout")[0]; @@ -11,6 +13,13 @@ let MastodonClientSecret = "mastodon_client_secret"; let MastodonAccessToken = "mastodon_access_token"; let MastodonTokenType = "mastodon_access_token"; +// Bluesky +let BlueskyApp = "https://bsky.app"; +let BlueskyPDS = "https://bsky.social"; + +// Tumblr +let TumblrWebsite = "https://www.tumblr.com"; + LoginButton.onclick = (event) => { MastodonAPI.HandleAuthentication(MastodonWebsite, MastodonClientID, MastodonClientSecret); } -- 2.45.3 From 2c643eed9e343f0fc46fbb93b75d202eb09c03d9 Mon Sep 17 00:00:00 2001 From: CatAClock Date: Thu, 24 Apr 2025 00:42:58 -0700 Subject: [PATCH 03/16] pick it up tomorrow --- JS/BlueskyAPI.js | 36 ++++++++++++++++++++++++++++++++++++ JS/index.js | 3 ++- JS/mail.js | 4 ++++ README.md | 5 ++++- oauth/README.md | 3 +++ oauth/client-metadata.json | 18 ++++++++++++++++++ 6 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 oauth/README.md create mode 100644 oauth/client-metadata.json diff --git a/JS/BlueskyAPI.js b/JS/BlueskyAPI.js index e69de29..7bd60ac 100644 --- a/JS/BlueskyAPI.js +++ b/JS/BlueskyAPI.js @@ -0,0 +1,36 @@ +export async function GetPDSWellKnown() { + let Data = await fetch("https://bsky.social/.well-known/oauth-authorization-server", {method: "GET"}) + .then((response) => response.json()); + return Data; +} + +export function CreatePKCE() { + let CodeVerifier = new Uint8Array(100); + let CodeChallenge; + + // generate a random string of characters. + crypto.getRandomValues(CodeVerifier); + console.log(CodeVerifier); + CodeVerifier = Uint8Array.toBase64({alphabet: "Base64url"}); + // Now generate a code challenge + CodeChallenge = sha256(CodeVerifier.toBase64({alphabet: "Base64url"})); + console.log(CodeVerifier); + console.log(CodeChallenge); +} + +// Stolen from elsewhere +// Firefox snippet +async function sha256(message) { + // encode as UTF-8 + const msgBuffer = new TextEncoder().encode(message); + + // hash the message + const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer); + + // convert ArrayBuffer to Array + const hashArray = Array.from(new Uint8Array(hashBuffer)); + + // convert bytes to hex string + const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); + return hashHex; +} diff --git a/JS/index.js b/JS/index.js index 4b26928..fee1ad9 100644 --- a/JS/index.js +++ b/JS/index.js @@ -19,6 +19,7 @@ const WarningClick = new Audio("Audio/button-pressed-38129.mp3"); const BackgroundMusic = new Audio("Audio/soft-piano-music-312509.mp3"); // Websites +let MastodonWebsite = "https://wetdry.world"; // Update a timer function UpdateTime() { @@ -108,7 +109,7 @@ async function PosterContainerUpdate() { let RemoteCookie = document.cookie.split("; ").find((row) => row.startsWith("Remote="))?.split("=")[1]; var RemoteTrue = (RemoteCookie === 'true'); - let Timeline = await ActivityPub.GetPublicTimeline(LocalTrue, RemoteTrue, MastodonWebsite); + let Timeline = await MastodonAPI.GetPublicTimeline(LocalTrue, RemoteTrue, MastodonWebsite); let Content = []; let Users = []; for (let i in Timeline) { diff --git a/JS/mail.js b/JS/mail.js index 352cc36..a468d97 100644 --- a/JS/mail.js +++ b/JS/mail.js @@ -91,3 +91,7 @@ PopulateNotifications(); function getRandomArbitrary(min, max) { return Math.random() * (max - min) + min; } + +// The next section is dedicated to testing. +// WARNING: I don't know what I am doing. +BlueskyAPI.CreatePKCE(); diff --git a/README.md b/README.md index 2eefe0c..a500a8b 100644 --- a/README.md +++ b/README.md @@ -5,4 +5,7 @@ A frontend to accessing my account on the fediverse ## technologies - node on a bare debian install. - npm and npx to execute it. -- currently only uses the Mastodon V1 API. \ No newline at end of file +- Uses MastodonAPI, BlueskyAPI, and TumblrAPI. +- Your local machine can also run it (with the proper dependencies; take a look at the docker file). + +Quick launch server without docker: `npx http-server /home//Documents/Fedi.CrowdedGames.Group --port 4000 --cors` diff --git a/oauth/README.md b/oauth/README.md new file mode 100644 index 0000000..d251bc4 --- /dev/null +++ b/oauth/README.md @@ -0,0 +1,3 @@ +# Bluesky stuff. + +Yeah. diff --git a/oauth/client-metadata.json b/oauth/client-metadata.json new file mode 100644 index 0000000..8109fa3 --- /dev/null +++ b/oauth/client-metadata.json @@ -0,0 +1,18 @@ +{ + "client_id": "https://fedi.crowdedgames.group/oauth/client-metadata.json", + "application_type": "web", + "client_name": "Example Browser App", + "client_uri": "https://fedi.crowdedgames.group", + "dpop_bound_access_tokens": true, + "grant_types": [ + "authorization_code", + "refresh_token" + ], + "redirect_uris": [ + "https://fedi.crowdedgames.group/HTML/mail.html" + ], + "response_types": [ + "code" + ], + "scope": "atproto transition:generic", +} -- 2.45.3 From 622309c6984949f7429b274dd06496eabc5e7bfd Mon Sep 17 00:00:00 2001 From: CatAClock Date: Thu, 24 Apr 2025 11:51:50 -0700 Subject: [PATCH 04/16] PKCE made --- JS/BlueskyAPI.js | 66 +++++++++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/JS/BlueskyAPI.js b/JS/BlueskyAPI.js index 7bd60ac..a141439 100644 --- a/JS/BlueskyAPI.js +++ b/JS/BlueskyAPI.js @@ -1,36 +1,50 @@ +// Component 1/7 export async function GetPDSWellKnown() { let Data = await fetch("https://bsky.social/.well-known/oauth-authorization-server", {method: "GET"}) .then((response) => response.json()); return Data; } -export function CreatePKCE() { - let CodeVerifier = new Uint8Array(100); - let CodeChallenge; - - // generate a random string of characters. - crypto.getRandomValues(CodeVerifier); - console.log(CodeVerifier); - CodeVerifier = Uint8Array.toBase64({alphabet: "Base64url"}); - // Now generate a code challenge - CodeChallenge = sha256(CodeVerifier.toBase64({alphabet: "Base64url"})); - console.log(CodeVerifier); - console.log(CodeChallenge); +// Component 2/7 +// 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; } -// Stolen from elsewhere -// Firefox snippet +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; +} + +// Stolen from elsewhere. +// Firefox snippet; Slightly edited. async function sha256(message) { - // encode as UTF-8 - const msgBuffer = new TextEncoder().encode(message); - - // hash the message - const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer); - - // convert ArrayBuffer to Array - const hashArray = Array.from(new Uint8Array(hashBuffer)); - - // convert bytes to hex string - const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); - return hashHex; + // 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; } -- 2.45.3 From ce1c02cd047d14d49ff4cf0609bda4711e83c105 Mon Sep 17 00:00:00 2001 From: CatAClock Date: Thu, 24 Apr 2025 15:12:56 -0700 Subject: [PATCH 05/16] ??? --- JS/BlueskyAPI.js | 21 +++++++++++++++++++++ JS/mail.js | 4 ++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/JS/BlueskyAPI.js b/JS/BlueskyAPI.js index a141439..c16faa9 100644 --- a/JS/BlueskyAPI.js +++ b/JS/BlueskyAPI.js @@ -28,6 +28,27 @@ export async function CreatePKCECodeChallenge(CodeVerifier) { return CodeChallenge; } +// Component 3/7 +export async function CreatePAR() { + let WellKnown = await GetPDSWellKnown(); + // Some verification mechanism with PAR + let PAREndpoint = WellKnown.pushed_authorization_request_endpoint; + + + // Token goes into the authorization endpoint + let AuthEndpoint = WellKnown.authorization_endpoint; +} + +// Component 4/7 +export async function ClientDPoP() { + let KeyPair = await crypto.subtle.generateKey({name: "ECDSA", namedCurve: "P-256"}, true, ["sign", "verify"]); + console.log(KeyPair); +} + +export async function ServerDPoP() { + +} + // Stolen from elsewhere. // Firefox snippet; Slightly edited. async function sha256(message) { diff --git a/JS/mail.js b/JS/mail.js index a468d97..0abf8b8 100644 --- a/JS/mail.js +++ b/JS/mail.js @@ -13,7 +13,7 @@ let MastodonClientSecret = "mastodon_client_secret"; let MastodonAccessToken = "mastodon_access_token"; let MastodonTokenType = "mastodon_access_token"; -// Bluesky +// Bluesky (TODO: use these variables). let BlueskyApp = "https://bsky.app"; let BlueskyPDS = "https://bsky.social"; @@ -94,4 +94,4 @@ function getRandomArbitrary(min, max) { // The next section is dedicated to testing. // WARNING: I don't know what I am doing. -BlueskyAPI.CreatePKCE(); +BlueskyAPI.ClientDPoP(); -- 2.45.3 From b9f6f45ac5baffe120a9c67bf2c2a73b39bdd78b Mon Sep 17 00:00:00 2001 From: CatAClock Date: Thu, 24 Apr 2025 15:54:22 -0700 Subject: [PATCH 06/16] JSRS saved teh day with JWT --- HTML/mail.html | 3 +++ JS/BlueskyAPI.js | 18 ++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/HTML/mail.html b/HTML/mail.html index f40e796..1e3f68c 100644 --- a/HTML/mail.html +++ b/HTML/mail.html @@ -6,6 +6,9 @@ + + diff --git a/JS/BlueskyAPI.js b/JS/BlueskyAPI.js index c16faa9..c6c28bf 100644 --- a/JS/BlueskyAPI.js +++ b/JS/BlueskyAPI.js @@ -41,8 +41,22 @@ export async function CreatePAR() { // Component 4/7 export async function ClientDPoP() { - let KeyPair = await crypto.subtle.generateKey({name: "ECDSA", namedCurve: "P-256"}, true, ["sign", "verify"]); - console.log(KeyPair); + // Header + var Header = {alg: 'HS256', typ: 'JWT', }; + // Payload + var Payload = {}; + var tNow = KJUR.jws.IntDate.get('now'); + Payload.iss = "http://foo.com"; + Payload.sub = "mailto:mike@foo.com"; + Payload.nbf = tNow; + Payload.iat = tNow; + Payload.jti = "id123456"; + Payload.aud = "http://foo.com/employee"; + // Sign JWT, password=616161 + var sHeader = JSON.stringify(Header); + var sPayload = JSON.stringify(Payload); + var JWT = KJUR.jws.JWS.sign("HS256", sHeader, sPayload, "616161"); + console.log(JWT); } export async function ServerDPoP() { -- 2.45.3 From a52478da54d326960afff84cb5191966d515cf5e Mon Sep 17 00:00:00 2001 From: CatAClock Date: Thu, 24 Apr 2025 17:14:32 -0700 Subject: [PATCH 07/16] commence testing. PAR and DPoP still need to be developed. --- JS/BlueskyAPI.js | 53 ++++++++++++++++++++++++++++++++++++------------ JS/mail.js | 27 ++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 13 deletions(-) diff --git a/JS/BlueskyAPI.js b/JS/BlueskyAPI.js index c6c28bf..da58237 100644 --- a/JS/BlueskyAPI.js +++ b/JS/BlueskyAPI.js @@ -1,11 +1,18 @@ -// Component 1/7 +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/7 +// 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 @@ -28,7 +35,7 @@ export async function CreatePKCECodeChallenge(CodeVerifier) { return CodeChallenge; } -// Component 3/7 +// Component 3/4 export async function CreatePAR() { let WellKnown = await GetPDSWellKnown(); // Some verification mechanism with PAR @@ -39,20 +46,20 @@ export async function CreatePAR() { let AuthEndpoint = WellKnown.authorization_endpoint; } -// Component 4/7 +// Component 4/4 export async function ClientDPoP() { + let KeyPair = await crypto.subtle.generateKey({name: "ECDSA", namedCurve: "P-256"}, true, ["sign", "verify"]); + // Header - var Header = {alg: 'HS256', typ: 'JWT', }; + var Header = {alg: "HS256", typ: "dpop+jwt", jwk: await crypto.subtle.exportKey("jwk", KeyPair.publicKey).then(function(response) {return response})}; // Payload var Payload = {}; - var tNow = KJUR.jws.IntDate.get('now'); - Payload.iss = "http://foo.com"; - Payload.sub = "mailto:mike@foo.com"; - Payload.nbf = tNow; - Payload.iat = tNow; - Payload.jti = "id123456"; - Payload.aud = "http://foo.com/employee"; - // Sign JWT, password=616161 + // Payload.jti + // Payload.htm + // Payload.htu + Payload.iat = Math.floor(new Date(Date.now()).getTime() / 1000); + // Payload.nonce + var sHeader = JSON.stringify(Header); var sPayload = JSON.stringify(Payload); var JWT = KJUR.jws.JWS.sign("HS256", sHeader, sPayload, "616161"); @@ -63,6 +70,26 @@ export async function ServerDPoP() { } +export async function AssertionJWT(BlueskyClientID) { + 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 = BlueskyClientID; + Payload.sub = BlueskyClientID; + // Payload.aud + // Payload.jti + 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"); + console.log(JWT); +} + // Stolen from elsewhere. // Firefox snippet; Slightly edited. async function sha256(message) { diff --git a/JS/mail.js b/JS/mail.js index 0abf8b8..e045226 100644 --- a/JS/mail.js +++ b/JS/mail.js @@ -95,3 +95,30 @@ function getRandomArbitrary(min, max) { // The next section is dedicated to testing. // WARNING: I don't know what I am doing. BlueskyAPI.ClientDPoP(); +BlueskyAPI.AssertionJWT("Nothing"); + +async function BlueskyTestingAuthorization() { + let WellKnown = await BlueskyAPI.GetPDSWellKnown(); + let PAREndpoint = WellKnown.pushed_authorization_request_endpoint; + + let TestingState = generateToken(64); + + let TestingVerifier = await BlueskyAPI.CreatePKCECodeVerifier() + let TestingChallenge = await BlueskyAPI.CreatePKCECodeChallenge(TestingVerifier); + + let TestingRequest = fetch(PAREndpoint + "?state=" + TestingState + "&pkceChallenge=" + TestingChallenge + "&scopes=atproto&login_hint=crowdedgames.group", {method: "POST"}); + console.log(TestingRequest); +} + +BlueskyTestingAuthorization(); + +// Stolen from Brave +// TODO: implement my own function. +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; +} -- 2.45.3 From a3498f5a033136b447fb004965313aa9ae530965 Mon Sep 17 00:00:00 2001 From: CatAClock Date: Mon, 28 Apr 2025 14:40:45 -0700 Subject: [PATCH 08/16] PROGRESS! A 201! --- JS/BlueskyAPI.js | 1 + JS/mail.js | 2 +- oauth/client-metadata.json | 5 +++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/JS/BlueskyAPI.js b/JS/BlueskyAPI.js index da58237..be0afb5 100644 --- a/JS/BlueskyAPI.js +++ b/JS/BlueskyAPI.js @@ -64,6 +64,7 @@ export async function ClientDPoP() { var sPayload = JSON.stringify(Payload); var JWT = KJUR.jws.JWS.sign("HS256", sHeader, sPayload, "616161"); console.log(JWT); + return JWT; } export async function ServerDPoP() { diff --git a/JS/mail.js b/JS/mail.js index e045226..e316836 100644 --- a/JS/mail.js +++ b/JS/mail.js @@ -106,7 +106,7 @@ async function BlueskyTestingAuthorization() { let TestingVerifier = await BlueskyAPI.CreatePKCECodeVerifier() let TestingChallenge = await BlueskyAPI.CreatePKCECodeChallenge(TestingVerifier); - let TestingRequest = fetch(PAREndpoint + "?state=" + TestingState + "&pkceChallenge=" + TestingChallenge + "&scopes=atproto&login_hint=crowdedgames.group", {method: "POST"}); + let TestingRequest = fetch(PAREndpoint, {method: "POST", body: new URLSearchParams({ response_type: "code", code_challenge_method: "S256", scope: "atproto", client_id: "https://fedi.crowdedgames.group/oauth/client-metadata.json", redirect_uri: "https://fedi.crowdedgames.group/HTML/mail.html", code_challenge: TestingChallenge, state: TestingState, login_hint: "crowdedgames.group" }), "Content-Type": "application/x-www-form-urlencoded"}); console.log(TestingRequest); } diff --git a/oauth/client-metadata.json b/oauth/client-metadata.json index 8109fa3..9ca5ace 100644 --- a/oauth/client-metadata.json +++ b/oauth/client-metadata.json @@ -1,7 +1,7 @@ { "client_id": "https://fedi.crowdedgames.group/oauth/client-metadata.json", "application_type": "web", - "client_name": "Example Browser App", + "client_name": "Channel Viewer", "client_uri": "https://fedi.crowdedgames.group", "dpop_bound_access_tokens": true, "grant_types": [ @@ -14,5 +14,6 @@ "response_types": [ "code" ], - "scope": "atproto transition:generic", + "token_endpoint_auth_method": "none", + "scope": "atproto transition:generic" } -- 2.45.3 From 9cc70382730f7ef3e1f19427704250bc51da7a08 Mon Sep 17 00:00:00 2001 From: CatAClock Date: Mon, 28 Apr 2025 15:22:29 -0700 Subject: [PATCH 09/16] Mastodon Bug Fixes --- HTML/mail.html | 8 ++++++-- JS/BlueskyAPI.js | 11 +++-------- JS/MastodonAPI.js | 22 +++++++++++----------- JS/mail.js | 33 ++++++++++++++++++++------------- 4 files changed, 40 insertions(+), 34 deletions(-) diff --git a/HTML/mail.html b/HTML/mail.html index 1e3f68c..7f38b5f 100644 --- a/HTML/mail.html +++ b/HTML/mail.html @@ -17,8 +17,12 @@

Mail

- - + + + + + +

Back

diff --git a/JS/BlueskyAPI.js b/JS/BlueskyAPI.js index be0afb5..20c6464 100644 --- a/JS/BlueskyAPI.js +++ b/JS/BlueskyAPI.js @@ -36,14 +36,9 @@ export async function CreatePKCECodeChallenge(CodeVerifier) { } // Component 3/4 -export async function CreatePAR() { - let WellKnown = await GetPDSWellKnown(); - // Some verification mechanism with PAR - let PAREndpoint = WellKnown.pushed_authorization_request_endpoint; - - - // Token goes into the authorization endpoint - let AuthEndpoint = WellKnown.authorization_endpoint; +export async function PARrequest(PAREndpoint, State, ChallengeCode) { + return await 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/mail.html", code_challenge: ChallengeCode, state: State, login_hint: "crowdedgames.group" }), "Content-Type": "application/x-www-form-urlencoded"}) + .then((response) => response.json()); } // Component 4/4 diff --git a/JS/MastodonAPI.js b/JS/MastodonAPI.js index c5b56ca..405b6b3 100644 --- a/JS/MastodonAPI.js +++ b/JS/MastodonAPI.js @@ -1,4 +1,5 @@ export const Scopes = "read write follow push"; +let Origin = location.href; // Gets the public timeline. export async function GetPublicTimeline(Local = false, Remote = false, Website) { @@ -66,22 +67,21 @@ export async function GetNotifications(Website, MastodonAccessToken, MastodonTok // The first step to using the app. export async function HandleAuthentication(Website, CookieClientID, CookieClientSecret) { // See if the user is smart enough to put https. - let InstanceData; - if (!Website.toLowerCase().split("://")[0].includes("https")) { - // Quickly check to see if it has something before :// so it doesn't screw the link. - if (Website.toLowerCase().split("://").length > 1) { - Website = "https://" + Website.split("://")[1]; - } else { - Website = "https://" + Website; - } - InstanceData = await fetch(Website + "/api/v1/apps?client_name=Channel Viewer&redirect_uris=" + Origin + "&scopes=" + ActivityPub.Scopes, {method: "POST"}) - .then((response) => response.json()); + let InstanceData = ""; + // Quickly check to see if it has something before :// so it doesn't screw the link. + if (Website.toLowerCase().split("://").length > 1) { + Website = "https://" + Website.split("://")[1]; + } else { + Website = "https://" + Website; } + console.log(Website); + InstanceData = await fetch(Website + "/api/v1/apps?client_name=Channel Viewer&redirect_uris=" + Origin + "&scopes=" + Scopes, {method: "POST"}) + .then((response) => response.json()); // Save the client stuff as cookies. document.cookie = CookieClientID + "=" + InstanceData.client_id + ";samesite=strict;path=/;expires=9999-01-01;"; document.cookie = CookieClientSecret + "=" + InstanceData.client_secret + ";samesite=strict;path=/;expires=9999-01-01;"; // Now authenticate the app. - document.location.href = Website + "/oauth/authorize?client_id=" + InstanceData.client_id + "&redirect_uri=" + Origin + "&response_type=code&scope=" + ActivityPub.Scopes; + document.location.href = Website + "/oauth/authorize?client_id=" + InstanceData.client_id + "&redirect_uri=" + Origin + "&response_type=code&scope=" + Scopes; } // This specific functino goes after HandleAuthentication for when login happens. diff --git a/JS/mail.js b/JS/mail.js index e316836..55e965f 100644 --- a/JS/mail.js +++ b/JS/mail.js @@ -2,9 +2,12 @@ import * as MastodonAPI from "./MastodonAPI.js"; import * as BlueskyAPI from "./BlueskyAPI.js"; import * as TumblrAPI from "./TumblrAPI.js"; -let LoginButton = document.getElementsByClassName("Login")[0]; -let LogoutButton = document.getElementsByClassName("Logout")[0]; -let Origin = window.location.origin + "/HTML/mail" +let MastodonLoginButton = document.getElementsByClassName("Login Mastodon")[0]; +let MastodonWebInput = document.getElementsByClassName("WebInput Mastodon")[0]; +let MastodonLogoutButton = document.getElementsByClassName("Logout Mastodon")[0]; +let BlueskyLoginButton = document.getElementsByClassName("Login Bluesky")[0]; +let BlueskyWebInput = document.getElementsByClassName("WebInput Bluesky")[0]; +let BlueskyLogoutButton = document.getElementsByClassName("Logout Bluesky")[0]; // Mastodon let MastodonWebsite = "https://wetdry.world"; @@ -20,11 +23,13 @@ let BlueskyPDS = "https://bsky.social"; // Tumblr let TumblrWebsite = "https://www.tumblr.com"; -LoginButton.onclick = (event) => { - MastodonAPI.HandleAuthentication(MastodonWebsite, MastodonClientID, MastodonClientSecret); +MastodonLoginButton.onclick = (event) => { + if (MastodonWebInput != "") { + MastodonAPI.HandleAuthentication(MastodonWebsite, MastodonClientID, MastodonClientSecret); + } } -LogoutButton.onclick = (event) => { +MastodonLogoutButton.onclick = (event) => { document.cookie = MastodonAccessToken + "=nothing;" + ";samesite=strict;path=/;expires=0000-01-01;"; document.cookie = MastodonTokenType + "=nothing;" + ";samesite=strict;path=/;expires=0000-01-01;"; console.log("Cleared the access token."); @@ -98,21 +103,23 @@ BlueskyAPI.ClientDPoP(); BlueskyAPI.AssertionJWT("Nothing"); async function BlueskyTestingAuthorization() { + // Declare Variables let WellKnown = await BlueskyAPI.GetPDSWellKnown(); let PAREndpoint = WellKnown.pushed_authorization_request_endpoint; - let TestingState = generateToken(64); + let State = generateToken(64); - let TestingVerifier = await BlueskyAPI.CreatePKCECodeVerifier() - let TestingChallenge = await BlueskyAPI.CreatePKCECodeChallenge(TestingVerifier); + let PKCEverifier = await BlueskyAPI.CreatePKCECodeVerifier(); + let PKCEchallenge = await BlueskyAPI.CreatePKCECodeChallenge(TestingVerifier); + + let PAR = BlueskyAPI.PARrequest(WellKnown.pushed_authorization_request_endpoint, State, PKCEchallenge); + console.log(PAR); - let TestingRequest = fetch(PAREndpoint, {method: "POST", body: new URLSearchParams({ response_type: "code", code_challenge_method: "S256", scope: "atproto", client_id: "https://fedi.crowdedgames.group/oauth/client-metadata.json", redirect_uri: "https://fedi.crowdedgames.group/HTML/mail.html", code_challenge: TestingChallenge, state: TestingState, login_hint: "crowdedgames.group" }), "Content-Type": "application/x-www-form-urlencoded"}); - console.log(TestingRequest); } -BlueskyTestingAuthorization(); +// BlueskyTestingAuthorization(); -// Stolen from Brave +// Stolen from Search // TODO: implement my own function. function generateToken(length) { var chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; -- 2.45.3 From dc1bcf5f6af8211749c16aa2166286de516f2826 Mon Sep 17 00:00:00 2001 From: CatAClock Date: Mon, 28 Apr 2025 16:15:51 -0700 Subject: [PATCH 10/16] remaining bug fixes. Sorry --- JS/MastodonAPI.js | 13 ++++++------ JS/mail.js | 54 ++++++++++++++++++++++++++++------------------- 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/JS/MastodonAPI.js b/JS/MastodonAPI.js index 405b6b3..9355bfb 100644 --- a/JS/MastodonAPI.js +++ b/JS/MastodonAPI.js @@ -1,5 +1,5 @@ export const Scopes = "read write follow push"; -let Origin = location.href; +let Origin = document.location.href; // Gets the public timeline. export async function GetPublicTimeline(Local = false, Remote = false, Website) { @@ -74,12 +74,11 @@ export async function HandleAuthentication(Website, CookieClientID, CookieClient } else { Website = "https://" + Website; } - console.log(Website); InstanceData = await fetch(Website + "/api/v1/apps?client_name=Channel Viewer&redirect_uris=" + Origin + "&scopes=" + Scopes, {method: "POST"}) .then((response) => response.json()); // Save the client stuff as cookies. - document.cookie = CookieClientID + "=" + InstanceData.client_id + ";samesite=strict;path=/;expires=9999-01-01;"; - document.cookie = CookieClientSecret + "=" + InstanceData.client_secret + ";samesite=strict;path=/;expires=9999-01-01;"; + document.cookie = CookieClientID + "=" + InstanceData.client_id + ";samesite=strict;path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT;"; + document.cookie = CookieClientSecret + "=" + InstanceData.client_secret + ";samesite=strict;path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT;"; // Now authenticate the app. document.location.href = Website + "/oauth/authorize?client_id=" + InstanceData.client_id + "&redirect_uri=" + Origin + "&response_type=code&scope=" + Scopes; } @@ -87,7 +86,7 @@ export async function HandleAuthentication(Website, CookieClientID, CookieClient // This specific functino goes after HandleAuthentication for when login happens. export async function GainToken(Website, CookieClientID, CookieClientSecret, CookieAccessToken, CookieTokenType) { // check if you both have a code and have a current authentication. - if (document.location.href.split("code=").length > 1 && document.cookie.split("; ").find((row) => row.startsWith(CookieClientID + "="))?.split("=") > 1) { + if (document.location.href.split("code=").length > 1 && document.cookie.split("; ").find((row) => row.startsWith(CookieClientID + "="))?.split("=").length > 1) { let code = document.location.href.split("code=")[1]; let ClientID = document.cookie.split("; ").find((row) => row.startsWith(CookieClientID + "="))?.split("=")[1]; let ClientSecret = document.cookie.split("; ").find((row) => row.startsWith(CookieClientSecret + "="))?.split("=")[1]; @@ -95,7 +94,7 @@ export async function GainToken(Website, CookieClientID, CookieClientSecret, Coo let AuthenticationToken = await fetch(Website + "/oauth/token?client_id=" + ClientID + "&client_secret=" + ClientSecret + "&redirect_uri=" + Origin + "&grant_type=authorization_code&code=" + code, {method: "POST"}) .then((response) => response.json()); // Cookify These - document.cookie = CookieAccessToken + "=" + AuthenticationToken.access_token + ";samesite=strict;path=/;expires=9999-01-01;"; - document.cookie = CookieTokenType + "=" + AuthenticationToken.token_type + ";samesite=strict;path=/;expires=9999-01-01;"; + document.cookie = CookieAccessToken + "=" + AuthenticationToken.access_token + ";samesite=strict;path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT;"; + document.cookie = CookieTokenType + "=" + AuthenticationToken.token_type + ";samesite=strict;path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT;"; } } diff --git a/JS/mail.js b/JS/mail.js index 55e965f..968d1cb 100644 --- a/JS/mail.js +++ b/JS/mail.js @@ -14,7 +14,7 @@ let MastodonWebsite = "https://wetdry.world"; let MastodonClientID = "mastodon_client_id"; let MastodonClientSecret = "mastodon_client_secret"; let MastodonAccessToken = "mastodon_access_token"; -let MastodonTokenType = "mastodon_access_token"; +let MastodonTokenType = "mastodon_token_type"; // Bluesky (TODO: use these variables). let BlueskyApp = "https://bsky.app"; @@ -30,24 +30,38 @@ MastodonLoginButton.onclick = (event) => { } MastodonLogoutButton.onclick = (event) => { - document.cookie = MastodonAccessToken + "=nothing;" + ";samesite=strict;path=/;expires=0000-01-01;"; - document.cookie = MastodonTokenType + "=nothing;" + ";samesite=strict;path=/;expires=0000-01-01;"; - console.log("Cleared the access token."); + document.cookie = MastodonClientID + "=nothing;" + ";samesite=strict;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;"; + document.cookie = MastodonClientSecret + "=nothing;" + ";samesite=strict;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;"; + document.cookie = MastodonAccessToken + "=nothing;" + ";samesite=strict;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;"; + document.cookie = MastodonTokenType + "=nothing;" + ";samesite=strict;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;"; + document.location.href = document.location.href; +} + +BlueskyLoginButton.onclick = (event) => { + if (BlueskyWebInput != "") { + BlueskyTestingAuthorization(); + } +} + +BlueskyLogoutButton.onclick = (event) => { + // Nothing at the moment } // if an access token is found, login. function CheckLogin() { - // Check for a token. - if (document.cookie.split("; ").find((row) => row.startsWith(MastodonAccessToken + "="))?.split("=").length > 1) { + // Check for a mastodon token. + if (document.cookie.split("; ").find((row) => row.startsWith(MastodonAccessToken + "="))?.split("=").length > 1 && !document.location.href.split("code=").length) { // Swap the buttons - LoginButton.remove(); - LogoutButton.setAttribute("style", ""); + MastodonLoginButton.remove(); + MastodonWebInput.remove(); + MastodonLogoutButton.setAttribute("style", ""); } + // Check for a bluesky token. } // Below is the thing it populates if you login. async function PopulateFavorites() { - let Favorites = MastodonAPI.GetFavorites(MastodonWebsite, MastodonAccessToken, MastodonTokenType); + let Favorites = await MastodonAPI.GetFavorites(MastodonWebsite, MastodonAccessToken, MastodonTokenType); let FavoritesArea = document.getElementsByClassName("Favorites")[0]; // Populate the favorites area. @@ -58,7 +72,7 @@ async function PopulateFavorites() { } async function PopulateBookmarks() { - let Bookmarks = MastodonAPI.GetBookmarks(MastodonWebsite, MastodonAccessToken, MastodonTokenType); + let Bookmarks = await MastodonAPI.GetBookmarks(MastodonWebsite, MastodonAccessToken, MastodonTokenType); let BookmarksArea = document.getElementsByClassName("Bookmarks")[0]; // Populate the Bookmarks area. @@ -69,7 +83,7 @@ async function PopulateBookmarks() { } async function PopulateNotifications() { - let Notifications = MastodonAPI.GetNotifications(MastodonWebsite, MastodonAccessToken, MastodonTokenType); + let Notifications = await MastodonAPI.GetNotifications(MastodonWebsite, MastodonAccessToken, MastodonTokenType); let NotificationsArea = document.getElementsByClassName("Notifications")[0]; // Populate the Conversations area. @@ -79,13 +93,12 @@ async function PopulateNotifications() { } } +await MastodonAPI.GainToken(MastodonWebsite, MastodonClientID, MastodonClientSecret, MastodonAccessToken, MastodonTokenType); + // Runs on website start. // Remove traces of "login". CheckLogin(); -// So far: login. TODO: Change this later. -MastodonAPI.GainToken(MastodonWebsite, MastodonClientID, MastodonClientSecret, MastodonAccessToken, MastodonTokenType); - // Populate the areas. PopulateFavorites(); PopulateBookmarks(); @@ -99,8 +112,6 @@ function getRandomArbitrary(min, max) { // The next section is dedicated to testing. // WARNING: I don't know what I am doing. -BlueskyAPI.ClientDPoP(); -BlueskyAPI.AssertionJWT("Nothing"); async function BlueskyTestingAuthorization() { // Declare Variables @@ -110,15 +121,14 @@ async function BlueskyTestingAuthorization() { let State = generateToken(64); let PKCEverifier = await BlueskyAPI.CreatePKCECodeVerifier(); - let PKCEchallenge = await BlueskyAPI.CreatePKCECodeChallenge(TestingVerifier); - - let PAR = BlueskyAPI.PARrequest(WellKnown.pushed_authorization_request_endpoint, State, PKCEchallenge); + let PKCEchallenge = await BlueskyAPI.CreatePKCECodeChallenge(PKCEverifier); + // PAR request (beginning) + let PAR = await BlueskyAPI.PARrequest(WellKnown.pushed_authorization_request_endpoint, State, PKCEchallenge); console.log(PAR); - + // 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=" + PAR.request_uri; } -// BlueskyTestingAuthorization(); - // Stolen from Search // TODO: implement my own function. function generateToken(length) { -- 2.45.3 From e6a9c0494460b7769b7d724dc863baebdfb45eeb Mon Sep 17 00:00:00 2001 From: CatAClock Date: Mon, 28 Apr 2025 16:51:00 -0700 Subject: [PATCH 11/16] my understanding hardens --- JS/BlueskyAPI.js | 41 +++++++++++++++++++++++++++-------------- JS/mail.js | 21 ++++++++------------- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/JS/BlueskyAPI.js b/JS/BlueskyAPI.js index 20c6464..4db456a 100644 --- a/JS/BlueskyAPI.js +++ b/JS/BlueskyAPI.js @@ -38,22 +38,25 @@ export async function CreatePKCECodeChallenge(CodeVerifier) { // Component 3/4 export async function PARrequest(PAREndpoint, State, ChallengeCode) { return await 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/mail.html", code_challenge: ChallengeCode, state: State, login_hint: "crowdedgames.group" }), "Content-Type": "application/x-www-form-urlencoded"}) - .then((response) => response.json()); + .then(function(response) { + console.log(response.headers.get("dpop-nonce")); + return response.json(); + }); } // Component 4/4 -export async function ClientDPoP() { +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: "HS256", typ: "dpop+jwt", jwk: await crypto.subtle.exportKey("jwk", KeyPair.publicKey).then(function(response) {return response})}; // Payload var Payload = {}; - // Payload.jti - // Payload.htm - // Payload.htu + Payload.jti = GenerateToken(64); + Payload.htm = POSTorGET; + Payload.htu = RequestURL; Payload.iat = Math.floor(new Date(Date.now()).getTime() / 1000); - // Payload.nonce + Payload.nonce = DPoPNonce; var sHeader = JSON.stringify(Header); var sPayload = JSON.stringify(Payload); @@ -62,11 +65,8 @@ export async function ClientDPoP() { return JWT; } -export async function ServerDPoP() { - -} - -export async function AssertionJWT(BlueskyClientID) { +// 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 @@ -74,16 +74,18 @@ export async function AssertionJWT(BlueskyClientID) { // Payload var Payload = {}; - Payload.iss = BlueskyClientID; - Payload.sub = BlueskyClientID; + Payload.iss = "https://fedi.crowdedgames.group/oauth/client-metadata.json"; + Payload.sub = "https://fedi.crowdedgames.group/oauth/client-metadata.json"; // Payload.aud - // Payload.jti + 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"); console.log(JWT); + console.log(KeyPair.publicKey); + console.log(KeyPair.privateKey); } // Stolen from elsewhere. @@ -106,3 +108,14 @@ async function sha256(message) { 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; +} diff --git a/JS/mail.js b/JS/mail.js index 968d1cb..c81f9a1 100644 --- a/JS/mail.js +++ b/JS/mail.js @@ -19,6 +19,11 @@ let MastodonTokenType = "mastodon_token_type"; // Bluesky (TODO: use these variables). let BlueskyApp = "https://bsky.app"; let BlueskyPDS = "https://bsky.social"; +let BlueskyPKCEverifer = "bluesky_pkce_verifier"; +let BlueskyPKCEchallenge = "bluesky_pkce_challenge"; +let BlueskyPrivateKey = "bluesky_private_key"; +let BlueskyPublicKey = "bluesky_public_key"; +let BlueskyNonce = "bluesky_nonce"; // Tumblr let TumblrWebsite = "https://www.tumblr.com"; @@ -112,13 +117,14 @@ function getRandomArbitrary(min, max) { // The next section is dedicated to testing. // WARNING: I don't know what I am doing. +await BlueskyAPI.AssertionJWT(); async function BlueskyTestingAuthorization() { // Declare Variables let WellKnown = await BlueskyAPI.GetPDSWellKnown(); let PAREndpoint = WellKnown.pushed_authorization_request_endpoint; - let State = generateToken(64); + let State = BlueskyAPI.GenerateToken(64); let PKCEverifier = await BlueskyAPI.CreatePKCECodeVerifier(); let PKCEchallenge = await BlueskyAPI.CreatePKCECodeChallenge(PKCEverifier); @@ -126,16 +132,5 @@ async function BlueskyTestingAuthorization() { let PAR = await BlueskyAPI.PARrequest(WellKnown.pushed_authorization_request_endpoint, State, PKCEchallenge); console.log(PAR); // 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=" + PAR.request_uri; -} - -// Stolen from Search -// TODO: implement my own function. -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; + // document.location.href = "https://bsky.social/oauth/authorize?client_id=https://fedi.crowdedgames.group/oauth/client-metadata.json&request_uri=" + PAR.request_uri; } -- 2.45.3 From df37d0d9537720a3b8bd3ecba3a63c1f5c03c190 Mon Sep 17 00:00:00 2001 From: CatAClock Date: Mon, 28 Apr 2025 17:39:50 -0700 Subject: [PATCH 12/16] moving things to the proper area; final bug fixes for mastodon --- HTML/mail.html | 11 +----- HTML/setting.html | 9 ++++- JS/MastodonAPI.js | 1 + JS/mail.js | 75 +----------------------------------- JS/setting.js | 96 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 109 insertions(+), 83 deletions(-) diff --git a/HTML/mail.html b/HTML/mail.html index 7f38b5f..90d1d5f 100644 --- a/HTML/mail.html +++ b/HTML/mail.html @@ -7,8 +7,7 @@ - + @@ -17,13 +16,7 @@

Mail

- - - - - - -

Back

+

OK

diff --git a/HTML/setting.html b/HTML/setting.html index ad0cd52..bb205b8 100644 --- a/HTML/setting.html +++ b/HTML/setting.html @@ -6,15 +6,22 @@ + +

Setting

-

Just go back. It ain't ready yet...

Toggle Local

Toggle Remote

+ + + + + +

OK

diff --git a/JS/MastodonAPI.js b/JS/MastodonAPI.js index 9355bfb..62a0920 100644 --- a/JS/MastodonAPI.js +++ b/JS/MastodonAPI.js @@ -74,6 +74,7 @@ export async function HandleAuthentication(Website, CookieClientID, CookieClient } else { Website = "https://" + Website; } + // Registering the app. InstanceData = await fetch(Website + "/api/v1/apps?client_name=Channel Viewer&redirect_uris=" + Origin + "&scopes=" + Scopes, {method: "POST"}) .then((response) => response.json()); // Save the client stuff as cookies. diff --git a/JS/mail.js b/JS/mail.js index c81f9a1..e038497 100644 --- a/JS/mail.js +++ b/JS/mail.js @@ -2,13 +2,7 @@ import * as MastodonAPI from "./MastodonAPI.js"; import * as BlueskyAPI from "./BlueskyAPI.js"; import * as TumblrAPI from "./TumblrAPI.js"; -let MastodonLoginButton = document.getElementsByClassName("Login Mastodon")[0]; -let MastodonWebInput = document.getElementsByClassName("WebInput Mastodon")[0]; -let MastodonLogoutButton = document.getElementsByClassName("Logout Mastodon")[0]; -let BlueskyLoginButton = document.getElementsByClassName("Login Bluesky")[0]; -let BlueskyWebInput = document.getElementsByClassName("WebInput Bluesky")[0]; -let BlueskyLogoutButton = document.getElementsByClassName("Logout Bluesky")[0]; - +// MAKE SURE THESE ARE SYNCED! // Mastodon let MastodonWebsite = "https://wetdry.world"; let MastodonClientID = "mastodon_client_id"; @@ -16,7 +10,7 @@ let MastodonClientSecret = "mastodon_client_secret"; let MastodonAccessToken = "mastodon_access_token"; let MastodonTokenType = "mastodon_token_type"; -// Bluesky (TODO: use these variables). +// Bluesky let BlueskyApp = "https://bsky.app"; let BlueskyPDS = "https://bsky.social"; let BlueskyPKCEverifer = "bluesky_pkce_verifier"; @@ -25,45 +19,6 @@ let BlueskyPrivateKey = "bluesky_private_key"; let BlueskyPublicKey = "bluesky_public_key"; let BlueskyNonce = "bluesky_nonce"; -// Tumblr -let TumblrWebsite = "https://www.tumblr.com"; - -MastodonLoginButton.onclick = (event) => { - if (MastodonWebInput != "") { - MastodonAPI.HandleAuthentication(MastodonWebsite, MastodonClientID, MastodonClientSecret); - } -} - -MastodonLogoutButton.onclick = (event) => { - document.cookie = MastodonClientID + "=nothing;" + ";samesite=strict;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;"; - document.cookie = MastodonClientSecret + "=nothing;" + ";samesite=strict;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;"; - document.cookie = MastodonAccessToken + "=nothing;" + ";samesite=strict;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;"; - document.cookie = MastodonTokenType + "=nothing;" + ";samesite=strict;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;"; - document.location.href = document.location.href; -} - -BlueskyLoginButton.onclick = (event) => { - if (BlueskyWebInput != "") { - BlueskyTestingAuthorization(); - } -} - -BlueskyLogoutButton.onclick = (event) => { - // Nothing at the moment -} - -// if an access token is found, login. -function CheckLogin() { - // Check for a mastodon token. - if (document.cookie.split("; ").find((row) => row.startsWith(MastodonAccessToken + "="))?.split("=").length > 1 && !document.location.href.split("code=").length) { - // Swap the buttons - MastodonLoginButton.remove(); - MastodonWebInput.remove(); - MastodonLogoutButton.setAttribute("style", ""); - } - // Check for a bluesky token. -} - // Below is the thing it populates if you login. async function PopulateFavorites() { let Favorites = await MastodonAPI.GetFavorites(MastodonWebsite, MastodonAccessToken, MastodonTokenType); @@ -98,12 +53,6 @@ async function PopulateNotifications() { } } -await MastodonAPI.GainToken(MastodonWebsite, MastodonClientID, MastodonClientSecret, MastodonAccessToken, MastodonTokenType); - -// Runs on website start. -// Remove traces of "login". -CheckLogin(); - // Populate the areas. PopulateFavorites(); PopulateBookmarks(); @@ -114,23 +63,3 @@ PopulateNotifications(); function getRandomArbitrary(min, max) { return Math.random() * (max - min) + min; } - -// The next section is dedicated to testing. -// WARNING: I don't know what I am doing. -await BlueskyAPI.AssertionJWT(); - -async function BlueskyTestingAuthorization() { - // Declare Variables - let WellKnown = await BlueskyAPI.GetPDSWellKnown(); - let PAREndpoint = WellKnown.pushed_authorization_request_endpoint; - - let State = BlueskyAPI.GenerateToken(64); - - let PKCEverifier = await BlueskyAPI.CreatePKCECodeVerifier(); - let PKCEchallenge = await BlueskyAPI.CreatePKCECodeChallenge(PKCEverifier); - // PAR request (beginning) - let PAR = await BlueskyAPI.PARrequest(WellKnown.pushed_authorization_request_endpoint, State, PKCEchallenge); - console.log(PAR); - // 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=" + PAR.request_uri; -} diff --git a/JS/setting.js b/JS/setting.js index e23c534..ac2b7e2 100644 --- a/JS/setting.js +++ b/JS/setting.js @@ -1,6 +1,39 @@ +import * as MastodonAPI from "./MastodonAPI.js"; +import * as BlueskyAPI from "./BlueskyAPI.js"; +import * as TumblrAPI from "./TumblrAPI.js"; + +// Settings buttons let LocalButton = document.getElementsByClassName("Local")[0]; let RemoteButton = document.getElementsByClassName("Remote")[0]; +let MastodonLoginButton = document.getElementsByClassName("Login Mastodon")[0]; +let MastodonWebInput = document.getElementsByClassName("WebInput Mastodon")[0]; +let MastodonLogoutButton = document.getElementsByClassName("Logout Mastodon")[0]; +let BlueskyLoginButton = document.getElementsByClassName("Login Bluesky")[0]; +let BlueskyWebInput = document.getElementsByClassName("WebInput Bluesky")[0]; +let BlueskyLogoutButton = document.getElementsByClassName("Logout Bluesky")[0]; + +// MAKE SURE THESE ARE SYNCED! +// Mastodon +let MastodonWebsite = "https://wetdry.world"; +let MastodonClientID = "mastodon_client_id"; +let MastodonClientSecret = "mastodon_client_secret"; +let MastodonAccessToken = "mastodon_access_token"; +let MastodonTokenType = "mastodon_token_type"; + +// Bluesky +let BlueskyApp = "https://bsky.app"; +let BlueskyPDS = "https://bsky.social"; +let BlueskyPKCEverifer = "bluesky_pkce_verifier"; +let BlueskyPKCEchallenge = "bluesky_pkce_challenge"; +let BlueskyPrivateKey = "bluesky_private_key"; +let BlueskyPublicKey = "bluesky_public_key"; +let BlueskyNonce = "bluesky_nonce"; + +// Tumblr +let TumblrWebsite = "https://www.tumblr.com"; + +// Change weather the timelines are public or remote LocalButton.onclick = (event) => { // Toggle the cookie if (document.cookie.split(";").some((item) => item.trim().startsWith("Local="))) { @@ -18,3 +51,66 @@ RemoteButton.onclick = (event) => { document.cookie = "Remote=true;samesite=strict;path=/;"; } } + +// Mastodon buttons +MastodonLoginButton.onclick = (event) => { + if (MastodonWebInput != "") { + MastodonAPI.HandleAuthentication(MastodonWebsite, MastodonClientID, MastodonClientSecret); + } +} + +MastodonLogoutButton.onclick = (event) => { + document.cookie = MastodonClientID + "=nothing;" + ";samesite=strict;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;"; + document.cookie = MastodonClientSecret + "=nothing;" + ";samesite=strict;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;"; + document.cookie = MastodonAccessToken + "=nothing;" + ";samesite=strict;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;"; + document.cookie = MastodonTokenType + "=nothing;" + ";samesite=strict;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;"; + document.location.href = document.location.href; +} + +// Bluesky Buttons +BlueskyLoginButton.onclick = (event) => { + if (BlueskyWebInput != "") { + BlueskyTestingAuthorization(); + } +} + +BlueskyLogoutButton.onclick = (event) => { + // Nothing at the moment +} + +// if an access token is found, login. +function CheckLogin() { + // Check for a mastodon token. + if (document.cookie.split("; ").find((row) => row.startsWith(MastodonAccessToken + "="))?.split("=").length > 1 || document.location.href.split("code=").length == 1) { + // Swap the buttons + MastodonLoginButton.remove(); + MastodonWebInput.remove(); + MastodonLogoutButton.setAttribute("style", ""); + } else { + MastodonAPI.GainToken(MastodonWebsite, MastodonClientID, MastodonClientSecret, MastodonAccessToken, MastodonTokenType); + } + // Check for a bluesky token. +} + +// Runs on website start. +// Remove traces of "login". +CheckLogin(); + +// The next section is dedicated to testing. +// WARNING: I don't know what I am doing. +await BlueskyAPI.AssertionJWT(); + +async function BlueskyTestingAuthorization() { + // Declare Variables + let WellKnown = await BlueskyAPI.GetPDSWellKnown(); + let PAREndpoint = WellKnown.pushed_authorization_request_endpoint; + + let State = BlueskyAPI.GenerateToken(64); + + let PKCEverifier = await BlueskyAPI.CreatePKCECodeVerifier(); + let PKCEchallenge = await BlueskyAPI.CreatePKCECodeChallenge(PKCEverifier); + // PAR request (beginning) + let PAR = await BlueskyAPI.PARrequest(WellKnown.pushed_authorization_request_endpoint, State, PKCEchallenge); + // 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=" + PAR.request_uri; +} -- 2.45.3 From 96432b006af0b7676e73e1c242a72f97d7495cdc Mon Sep 17 00:00:00 2001 From: CatAClock Date: Mon, 28 Apr 2025 19:23:37 -0700 Subject: [PATCH 13/16] more things --- HTML/setting.html | 2 +- JS/BlueskyAPI.js | 46 +++++++++++++++++++++++++++++--------- JS/mail.js | 1 - JS/setting.js | 38 +++++++++++++------------------ oauth/client-metadata.json | 2 +- 5 files changed, 54 insertions(+), 35 deletions(-) diff --git a/HTML/setting.html b/HTML/setting.html index bb205b8..b99464a 100644 --- a/HTML/setting.html +++ b/HTML/setting.html @@ -21,7 +21,7 @@ - +

OK

diff --git a/JS/BlueskyAPI.js b/JS/BlueskyAPI.js index 4db456a..d8d0305 100644 --- a/JS/BlueskyAPI.js +++ b/JS/BlueskyAPI.js @@ -4,7 +4,6 @@ export async function GetBlueskyDID(PDS, Handle) { return request; } - // Component 1/4 export async function GetPDSWellKnown() { let Data = await fetch("https://bsky.social/.well-known/oauth-authorization-server", {method: "GET"}) @@ -37,15 +36,11 @@ export async function CreatePKCECodeChallenge(CodeVerifier) { // Component 3/4 export async function PARrequest(PAREndpoint, State, ChallengeCode) { - return await 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/mail.html", code_challenge: ChallengeCode, state: State, login_hint: "crowdedgames.group" }), "Content-Type": "application/x-www-form-urlencoded"}) - .then(function(response) { - console.log(response.headers.get("dpop-nonce")); - return response.json(); - }); + 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" }), "Content-Type": "application/x-www-form-urlencoded"}); } // Component 4/4 -export async function ClientDPoP(POSTorGET, RequestURL, DPoPNonce) { +export async function ClientDPoP(POSTorGET, RequestURL, DPoPNonce, publicKey) { let KeyPair = await crypto.subtle.generateKey({name: "ECDSA", namedCurve: "P-256"}, true, ["sign", "verify"]); // Header @@ -83,9 +78,39 @@ export async function AssertionJWT() { var sHeader = JSON.stringify(Header); var sPayload = JSON.stringify(Payload); var JWT = KJUR.jws.JWS.sign("HS256", sHeader, sPayload, "838383"); - console.log(JWT); - console.log(KeyPair.publicKey); - console.log(KeyPair.privateKey); +} + +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() { + } // Stolen from elsewhere. @@ -119,3 +144,4 @@ export function GenerateToken(length) { } return token; } + diff --git a/JS/mail.js b/JS/mail.js index e038497..86f3f02 100644 --- a/JS/mail.js +++ b/JS/mail.js @@ -15,7 +15,6 @@ let BlueskyApp = "https://bsky.app"; let BlueskyPDS = "https://bsky.social"; let BlueskyPKCEverifer = "bluesky_pkce_verifier"; let BlueskyPKCEchallenge = "bluesky_pkce_challenge"; -let BlueskyPrivateKey = "bluesky_private_key"; let BlueskyPublicKey = "bluesky_public_key"; let BlueskyNonce = "bluesky_nonce"; diff --git a/JS/setting.js b/JS/setting.js index ac2b7e2..6170cd3 100644 --- a/JS/setting.js +++ b/JS/setting.js @@ -26,9 +26,10 @@ let BlueskyApp = "https://bsky.app"; let BlueskyPDS = "https://bsky.social"; let BlueskyPKCEverifer = "bluesky_pkce_verifier"; let BlueskyPKCEchallenge = "bluesky_pkce_challenge"; -let BlueskyPrivateKey = "bluesky_private_key"; let BlueskyPublicKey = "bluesky_public_key"; let BlueskyNonce = "bluesky_nonce"; +let BlueskyAccessToken = "bluesky_access_token"; +let BlueskyRefreshToken = "bluesky_refresh_token"; // Tumblr let TumblrWebsite = "https://www.tumblr.com"; @@ -70,12 +71,14 @@ MastodonLogoutButton.onclick = (event) => { // Bluesky Buttons BlueskyLoginButton.onclick = (event) => { if (BlueskyWebInput != "") { - BlueskyTestingAuthorization(); + BlueskyAPI.HandleAuthorization(BlueskyPKCEverifer, BlueskyPKCEchallenge, BlueskyNonce); } } BlueskyLogoutButton.onclick = (event) => { - // Nothing at the moment + document.cookie = BlueskyPKCEverifer + "=nothing;" + ";samesite=strict;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;"; + document.cookie = BlueskyPKCEchallenge + "=nothing;" + ";samesite=strict;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;"; + document.cookie = BlueskyNonce + "=nothing;" + ";samesite=strict;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;"; } // if an access token is found, login. @@ -87,30 +90,21 @@ function CheckLogin() { MastodonWebInput.remove(); MastodonLogoutButton.setAttribute("style", ""); } else { + // Auto log in MastodonAPI.GainToken(MastodonWebsite, MastodonClientID, MastodonClientSecret, MastodonAccessToken, MastodonTokenType); } + 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(BlueskyAccessToken + "="))?.split("=").length > 1) { + // Swap the buttons + BlueskyLoginButton.remove(); + BlueskyWebInput.remove(); + BlueskyLogoutButton.setAttribute("style", ""); + } else { + // Auto log in + BlueskyAPI.GainTokens(); + } // Check for a bluesky token. } // Runs on website start. // Remove traces of "login". CheckLogin(); - -// The next section is dedicated to testing. -// WARNING: I don't know what I am doing. -await BlueskyAPI.AssertionJWT(); - -async function BlueskyTestingAuthorization() { - // Declare Variables - let WellKnown = await BlueskyAPI.GetPDSWellKnown(); - let PAREndpoint = WellKnown.pushed_authorization_request_endpoint; - - let State = BlueskyAPI.GenerateToken(64); - - let PKCEverifier = await BlueskyAPI.CreatePKCECodeVerifier(); - let PKCEchallenge = await BlueskyAPI.CreatePKCECodeChallenge(PKCEverifier); - // PAR request (beginning) - let PAR = await BlueskyAPI.PARrequest(WellKnown.pushed_authorization_request_endpoint, State, PKCEchallenge); - // 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=" + PAR.request_uri; -} diff --git a/oauth/client-metadata.json b/oauth/client-metadata.json index 9ca5ace..4075d9b 100644 --- a/oauth/client-metadata.json +++ b/oauth/client-metadata.json @@ -9,7 +9,7 @@ "refresh_token" ], "redirect_uris": [ - "https://fedi.crowdedgames.group/HTML/mail.html" + "https://fedi.crowdedgames.group/HTML/setting.html" ], "response_types": [ "code" -- 2.45.3 From 7262b2ec32a148726dad048fa9847d8317e33644 Mon Sep 17 00:00:00 2001 From: CatAClock Date: Tue, 29 Apr 2025 14:14:10 -0700 Subject: [PATCH 14/16] 401 error now --- JS/BlueskyAPI.js | 24 ++++++++++++++++++------ JS/setting.js | 7 ++++--- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/JS/BlueskyAPI.js b/JS/BlueskyAPI.js index d8d0305..771f341 100644 --- a/JS/BlueskyAPI.js +++ b/JS/BlueskyAPI.js @@ -36,26 +36,31 @@ export async function CreatePKCECodeChallenge(CodeVerifier) { // 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" }), "Content-Type": "application/x-www-form-urlencoded"}); + 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, publicKey) { +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: "HS256", typ: "dpop+jwt", jwk: await crypto.subtle.exportKey("jwk", KeyPair.publicKey).then(function(response) {return response})}; + 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("HS256", sHeader, sPayload, "616161"); + 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; } @@ -109,8 +114,15 @@ export async function HandleAuthorization(BlueskyPKCEverifer, BlueskyPKCEchallen 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() { - +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. diff --git a/JS/setting.js b/JS/setting.js index 6170cd3..811b3bc 100644 --- a/JS/setting.js +++ b/JS/setting.js @@ -79,12 +79,13 @@ BlueskyLogoutButton.onclick = (event) => { document.cookie = BlueskyPKCEverifer + "=nothing;" + ";samesite=strict;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;"; document.cookie = BlueskyPKCEchallenge + "=nothing;" + ";samesite=strict;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;"; document.cookie = BlueskyNonce + "=nothing;" + ";samesite=strict;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;"; + document.location.href = document.location.href; } // if an access token is found, login. function CheckLogin() { // Check for a mastodon token. - if (document.cookie.split("; ").find((row) => row.startsWith(MastodonAccessToken + "="))?.split("=").length > 1 || document.location.href.split("code=").length == 1) { + if (document.cookie.split("; ").find((row) => row.startsWith(MastodonAccessToken + "="))?.split("=").length > 1 && document.location.href.split("code=").length == 1) { // Swap the buttons MastodonLoginButton.remove(); MastodonWebInput.remove(); @@ -93,14 +94,14 @@ function CheckLogin() { // Auto log in MastodonAPI.GainToken(MastodonWebsite, MastodonClientID, MastodonClientSecret, MastodonAccessToken, MastodonTokenType); } - 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(BlueskyAccessToken + "="))?.split("=").length > 1) { + 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(BlueskyAccessToken + "="))?.split("=").length > 1) { // Swap the buttons BlueskyLoginButton.remove(); BlueskyWebInput.remove(); BlueskyLogoutButton.setAttribute("style", ""); } else { // Auto log in - BlueskyAPI.GainTokens(); + BlueskyAPI.GainTokens(BlueskyPKCEchallenge, BlueskyNonce); } // Check for a bluesky token. } -- 2.45.3 From f95acf0deb6390008ccbcb53a94a8bd481704044 Mon Sep 17 00:00:00 2001 From: CatAClock Date: Tue, 29 Apr 2025 14:57:13 -0700 Subject: [PATCH 15/16] WE GOT TOKENS BABY! --- JS/BlueskyAPI.js | 49 ++++++++++++++++++++++++------------------------ JS/setting.js | 2 +- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/JS/BlueskyAPI.js b/JS/BlueskyAPI.js index 771f341..57f92d1 100644 --- a/JS/BlueskyAPI.js +++ b/JS/BlueskyAPI.js @@ -40,7 +40,8 @@ export async function PARrequest(PAREndpoint, State, ChallengeCode) { } 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"}}); + 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"}}) + .then((response) => response.json()); } // Component 4/4 @@ -48,20 +49,33 @@ 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})}; + var Header = {typ: "dpop+jwt", alg: "ES256", jwk: + await crypto.subtle.exportKey("jwk", KeyPair.publicKey) + .then(function(response) { + delete response["key_ops"]; + delete response["ext"]; + delete response["alg"]; + return response}) + }; // Payload var Payload = {}; - Payload.jti = GenerateToken(64); + Payload.iss = "https://fedi.crowdedgames.group/oauth/client-metadata.json"; + Payload.jti = crypto.randomUUID(); 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); + var JWT = KJUR.jws.JWS.sign("ES256", sHeader, sPayload, + await crypto.subtle.exportKey("jwk", KeyPair.privateKey) + .then(function(response) { + delete response["key_ops"]; + delete response["ext"]; + delete response["alg"]; + return response}) + ); return JWT; } @@ -77,7 +91,7 @@ export async function AssertionJWT() { 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.jti = crypto.randomUUID(); Payload.iat = Math.floor(new Date(Date.now()).getTime() / 1000); var sHeader = JSON.stringify(Header); @@ -92,7 +106,7 @@ export async function HandleAuthorization(BlueskyPKCEverifer, BlueskyPKCEchallen let WellKnown = await GetPDSWellKnown(); let PAREndpoint = WellKnown.pushed_authorization_request_endpoint; - let State = GenerateToken(64); + let State = crypto.randomUUID(); let PKCEverifier = await CreatePKCECodeVerifier(); let PKCEchallenge = await CreatePKCECodeChallenge(PKCEverifier); @@ -116,12 +130,11 @@ export async function HandleAuthorization(BlueskyPKCEverifer, BlueskyPKCEchallen 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 DPoP = await 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); + let Auth = await AuthRequest("https://bsky.social/oauth/token", PKCE, code, DPoP); + console.log(Auth); } } @@ -145,15 +158,3 @@ async function sha256(message) { 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; -} - diff --git a/JS/setting.js b/JS/setting.js index 811b3bc..6a43cd1 100644 --- a/JS/setting.js +++ b/JS/setting.js @@ -101,7 +101,7 @@ function CheckLogin() { BlueskyLogoutButton.setAttribute("style", ""); } else { // Auto log in - BlueskyAPI.GainTokens(BlueskyPKCEchallenge, BlueskyNonce); + BlueskyAPI.GainTokens(BlueskyPKCEverifer, BlueskyNonce); } // Check for a bluesky token. } -- 2.45.3 From 550e7d1a7ef63e93248df130dde2672b19cc5248 Mon Sep 17 00:00:00 2001 From: CatAClock Date: Tue, 29 Apr 2025 15:05:09 -0700 Subject: [PATCH 16/16] Done! --- JS/BlueskyAPI.js | 11 ++++++++--- JS/setting.js | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/JS/BlueskyAPI.js b/JS/BlueskyAPI.js index 57f92d1..96457b0 100644 --- a/JS/BlueskyAPI.js +++ b/JS/BlueskyAPI.js @@ -128,13 +128,18 @@ export async function HandleAuthorization(BlueskyPKCEverifer, BlueskyPKCEchallen 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) { +export async function GainTokens(PKCEcodeName, NonceName, AccessToken, RefreshToken) { + // Check to see if something's a miss... + 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 && document.location.href.split("code=").length > 1) && document.cookie.split("; ").find((row) => row.startsWith(AccessToken + "="))?.split("=").length == 1) { + // Create varaibles, be aware of waits because of internet. let DPoP = await 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]; + // Authentication let Auth = await AuthRequest("https://bsky.social/oauth/token", PKCE, code, DPoP); - console.log(Auth); + // Save the tokens and be done + document.cookie = AccessToken + "=" + Auth.access_token + ";samesite=strict;path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT;"; + document.cookie = RefreshToken + "=" + Auth.refresh_token + ";samesite=strict;path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT;"; } } diff --git a/JS/setting.js b/JS/setting.js index 6a43cd1..57d122b 100644 --- a/JS/setting.js +++ b/JS/setting.js @@ -101,7 +101,7 @@ function CheckLogin() { BlueskyLogoutButton.setAttribute("style", ""); } else { // Auto log in - BlueskyAPI.GainTokens(BlueskyPKCEverifer, BlueskyNonce); + BlueskyAPI.GainTokens(BlueskyPKCEverifer, BlueskyNonce, BlueskyAccessToken, BlueskyRefreshToken); } // Check for a bluesky token. } -- 2.45.3