diff --git a/CSS/index.css b/CSS/index.css index 0256dc6..ee4f788 100644 --- a/CSS/index.css +++ b/CSS/index.css @@ -205,6 +205,13 @@ html { border-width: 1px; } +.Posting { + border-style: solid; + border-width: 1px; + + margin-right: 5%; +} + .MainFooter { display: flex; margin-top: 75vh; diff --git a/CSS/post.css b/CSS/post.css new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/CSS/post.css @@ -0,0 +1 @@ + diff --git a/CSS/setting.css b/CSS/setting.css new file mode 100644 index 0000000..e69de29 diff --git a/HTML/mail.html b/HTML/mail.html index 90d1d5f..786e0f6 100644 --- a/HTML/mail.html +++ b/HTML/mail.html @@ -16,7 +16,7 @@

Mail

-

OK

+

Back

diff --git a/HTML/post.html b/HTML/post.html new file mode 100644 index 0000000..3ab1a55 --- /dev/null +++ b/HTML/post.html @@ -0,0 +1,21 @@ + + + + The Fediverse + + + + + + + + + +
+

Post

+ +

POST!

+
+

Back

+ + diff --git a/HTML/setting.html b/HTML/setting.html index b99464a..e3ff4a9 100644 --- a/HTML/setting.html +++ b/HTML/setting.html @@ -4,7 +4,7 @@ The Fediverse - + @@ -17,11 +17,11 @@

Toggle Local

Toggle Remote

Login to Mastodon

- +

Login to Bluesky

- + -

OK

+

Back

diff --git a/JS/BlueskyAPI.js b/JS/BlueskyAPI.js index f294473..9a3abcf 100644 --- a/JS/BlueskyAPI.js +++ b/JS/BlueskyAPI.js @@ -1,14 +1,54 @@ +import * as Cookie from "./Cookies.js"; + 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; + let DPoP = await ClientDPoPPDS("GET", PDS + "/xrpc/com.atproto.identity.resolveHandle?handle=" + Handle); + let request = fetch(PDS + "/xrpc/com.atproto.identity.resolveHandle?handle=" + Handle, { method: "GET", headers: {"Authorization": "DPoP " + Cookie.BlueskyAccessTokenCookie, "DPoP": DPoP}}); + let body = await request.then((response) => response.json()); + let status = await request.then((response) => response.status); + let header = await request.then((response) => response.headers.get("dpop-nonce")); + if (status == 401) { + await FixNonceMismatch(header); + request = fetch(PDS + "/xrpc/com.atproto.identity.resolveHandle?handle=" + Handle, { method: "GET", headers: {"Authorization": "DPoP " + Cookie.BlueskyAccessTokenCookie, "DPoP": DPoP}}); + body = await request.then((response) => response.json()); + } + return body; +} + +export async function CreatePost(PDS, DID, Text) { + let Json = { + "$type": "app.bsky.feed.post", + "text": Text, + "createdAt": new Date(Date.now()).toISOString() + } + let RequestBody = { + "repo": DID, + "collection": "app.bsky.feed.post", + "record": Json + } + console.log(DID); + console.log(RequestBody.repo); + let DPoP = await ClientDPoPPDS("POST", PDS + "/xrpc/com.atproto.repo.createRecord"); + let request = fetch(PDS + "/xrpc/com.atproto.repo.createRecord", { body: JSON.stringify(RequestBody), method: "POST", headers: {"Content-Type": "application/json", "Authorization": "DPoP " + Cookie.BlueskyAccessTokenCookie, "DPoP": DPoP}}); + let body = await request.then((response) => response.json()); + let status = await request.then((response) => response.status); + let header = await request.then((response) => response.headers.get("dpop-nonce")); + if (status == 401) { + await FixNonceMismatch(header); + let request = fetch(PDS + "/xrpc/com.atproto.repo.createRecord", { body: JSON.stringify(RequestBody), method: "POST", headers: {"Content-Type": "application/json", "Authorization": "DPoP " + Cookie.BlueskyAccessTokenCookie, "DPoP": DPoP}}); + body = await request.then((response) => response.json()); + } + return body; +} + +// Added after all the components: in case of nonce mismatch... +export async function FixNonceMismatch(head) { + return Cookie.InputCookie(Cookie.BlueskyNonceName, head); } // Component 1/4 export async function GetPDSWellKnown() { - let Data = await fetch("https://bsky.social/.well-known/oauth-authorization-server", {method: "GET"}) + return await fetch("https://bsky.social/.well-known/oauth-authorization-server", {method: "GET"}) .then((response) => response.json()); - return Data; } // Component 2/4 @@ -35,22 +75,23 @@ 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" }), headers: {"Content-Type": "application/x-www-form-urlencoded"}}); +export async function PARrequest(PAREndpoint, State, Challenge) { + 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: Challenge, 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"}}) +export async function AuthRequest(TokenEndpoint, code, DPoP, Verify) { + 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: Verify}), headers: { "DPoP": DPoP, "Content-Type": "application/x-www-form-urlencoded"}}) .then((response) => response.json()); } // Component 4/4 -export async function ClientDPoP(POSTorGET, RequestURL, DPoPNonce) { - let KeyPair = await crypto.subtle.generateKey({name: "ECDSA", namedCurve: "P-256"}, true, ["sign", "verify"]); +export async function ClientDPoPToken(POSTorGET, RequestURL) { + let PublicKey = await crypto.subtle.importKey("jwk", JSON.parse(Cookie.BlueskyPublicKeyCookie), {name: "ECDSA", namedCurve: "P-256"}, true, ["verify"]); + let PrivateKey = await crypto.subtle.importKey("jwk", JSON.parse(Cookie.BlueskyPrivateKeyCookie), {name: "ECDSA", namedCurve: "P-256"}, true, ["sign"]); // Header var Header = {typ: "dpop+jwt", alg: "ES256", jwk: - await crypto.subtle.exportKey("jwk", KeyPair.publicKey) + await crypto.subtle.exportKey("jwk", PublicKey) .then(function(response) { delete response["key_ops"]; delete response["ext"]; @@ -64,12 +105,12 @@ export async function ClientDPoP(POSTorGET, RequestURL, DPoPNonce) { Payload.htm = POSTorGET; Payload.htu = RequestURL; Payload.iat = Math.floor(new Date(Date.now()).getTime() / 1000); - Payload.nonce = DPoPNonce; + Payload.nonce = Cookie.BlueskyNonceCookie; 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) + await crypto.subtle.exportKey("jwk", PrivateKey) .then(function(response) { delete response["key_ops"]; delete response["ext"]; @@ -80,66 +121,85 @@ export async function ClientDPoP(POSTorGET, RequestURL, DPoPNonce) { } // 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"]); +export async function ClientDPoPPDS(POSTorGET, RequestURL) { + let PublicKey = await crypto.subtle.importKey("jwk", JSON.parse(Cookie.BlueskyPublicKeyCookie), {name: "ECDSA", namedCurve: "P-256"}, true, ["verify"]); + let PrivateKey = await crypto.subtle.importKey("jwk", JSON.parse(Cookie.BlueskyPrivateKeyCookie), {name: "ECDSA", namedCurve: "P-256"}, true, ["sign"]); // Header - var Header = {alg: "HS256", kid: 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", PublicKey) + .then(function(response) { + delete response["key_ops"]; + delete response["ext"]; + delete response["alg"]; + 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 = crypto.randomUUID(); + Payload.htm = POSTorGET; + Payload.htu = RequestURL; Payload.iat = Math.floor(new Date(Date.now()).getTime() / 1000); + Payload.nonce = Cookie.BlueskyNonceCookie; + Payload.ath = await CreatePKCECodeChallenge(Cookie.BlueskyAccessTokenCookie); var sHeader = JSON.stringify(Header); var sPayload = JSON.stringify(Payload); - var JWT = KJUR.jws.JWS.sign("HS256", sHeader, sPayload, "838383"); + var JWT = KJUR.jws.JWS.sign("ES256", sHeader, sPayload, + await crypto.subtle.exportKey("jwk", PrivateKey) + .then(function(response) { + delete response["key_ops"]; + delete response["ext"]; + delete response["alg"]; + return response}) + ); + return JWT; } -export async function HandleAuthorization(BlueskyPKCEverifer, BlueskyPKCEchallenge, BlueskyNonce) { +export async function HandleAuthorization() { // 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 = crypto.randomUUID(); let PKCEverifier = await CreatePKCECodeVerifier(); let PKCEchallenge = await CreatePKCECodeChallenge(PKCEverifier); + // Save these + Cookie.InputCookie(Cookie.BlueskyPKCEVeriferName, PKCEverifier); + Cookie.InputCookie(Cookie.BlueskyPKCEChallengeName, PKCEchallenge); // 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;"; - // } + // Save nonce + Cookie.InputCookie(Cookie.BlueskyNonceName, nonce); + // Export keys + let ExportedKey1 = await crypto.subtle.exportKey("jwk", KeyPair.publicKey); + let ExportedKey2 = await crypto.subtle.exportKey("jwk", KeyPair.privateKey); + // Convert them into a good format. + Cookie.InputCookie(Cookie.BlueskyPublicKeyName, JSON.stringify(ExportedKey1)); + Cookie.InputCookie(Cookie.BlueskyPrivateKeyName, JSON.stringify(ExportedKey2)); // 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, AccessToken, RefreshToken) { +export async function GainTokens() { + let WellKnown = await GetPDSWellKnown(); + // 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.cookie.split("; ").find((row) => row.startsWith(AccessToken + "="))?.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) && Cookie.IsCookieReal(Cookie.BlueskyPKCEVeriferCookie) && !(Cookie.IsCookieReal(Cookie.BlueskyAccessTokenCookie))) { // 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 DPoP = await ClientDPoPToken("POST", WellKnown.token_endpoint); let code = document.location.href.split("code=")[1]; // Authentication - let Auth = await AuthRequest("https://bsky.social/oauth/token", PKCE, code, DPoP); + let cookie = await Cookie.BlueskyPKCEVeriferCookie; + let Auth = await AuthRequest(WellKnown.token_endpoint, code, DPoP, cookie); // 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;"; + Cookie.InputCookie(Cookie.BlueskyAccessTokenName, Auth.access_token); + Cookie.InputCookie(Cookie.BlueskyRefreshTokenName, Auth.refresh_token); } } @@ -163,3 +223,18 @@ async function sha256(message) { return string; } + +// Firefox snippet. +function ab2str(buf) { + return String.fromCharCode.apply(null, new Uint8Array(buf)); +} + +// Firefox snippet. +function str2ab(str) { + const buf = new ArrayBuffer(str.length); + const bufView = new Uint8Array(buf); + for (let i = 0, strLen = str.length; i < strLen; i++) { + bufView[i] = str.charCodeAt(i); + } + return buf; +} diff --git a/JS/Cookies.js b/JS/Cookies.js new file mode 100644 index 0000000..800013c --- /dev/null +++ b/JS/Cookies.js @@ -0,0 +1,84 @@ +// STRINGS TODO: make sure to seperate stuff that a user will want to input. Ex: MastodonWebsiteName, BlueskyPDS, etc. +// Mastodon +export const MastodonWebsiteName = "mastodon_website"; +export const MastodonClientIDName = "mastodon_client_id"; +export const MastodonClientSecretName = "mastodon_client_secret"; +export const MastodonAccessTokenName = "mastodon_access_token"; +export const MastodonTokenTypeName = "mastodon_token_type"; + +// Bluesky +export const BlueskyAppName = "https://bsky.app"; +export const BlueskyPDSName = "https://bsky.social"; +export const BlueskyPKCEVeriferName = "bluesky_pkce_verifier"; +export const BlueskyPKCEChallengeName = "bluesky_pkce_challenge"; +export const BlueskyPublicKeyName = "bluesky_public_key"; +export const BlueskyPrivateKeyName = "bluesky_private_key"; +export const BlueskyNonceName = "bluesky_nonce"; +export const BlueskyAccessTokenName = "bluesky_access_token"; +export const BlueskyRefreshTokenName = "bluesky_refresh_token"; + +// Tumblr +export const TumblrWebsiteName = "https://www.tumblr.com"; + +// COOKIES TODO: person inputted stuff (like MastodonWebsiteName) should be implemented. +// Mastodon +export const MastodonWebsiteCookie = GetCookie(MastodonWebsiteName); +export const MastodonClientIDCookie = GetCookie(MastodonClientIDName); +export const MastodonClientSecretCookie = GetCookie(MastodonClientSecretName); +export const MastodonAccessTokenCookie = GetCookie(MastodonAccessTokenName); +export const MastodonTokenTypeCookie = GetCookie(MastodonTokenTypeName); + +// Bluesky +export const BlueskyPKCEVeriferCookie = GetCookie(BlueskyPKCEVeriferName); +export const BlueskyPKCEChallengeCookie = GetCookie(BlueskyPKCEChallengeName); +export const BlueskyPublicKeyCookie = GetCookie(BlueskyPublicKeyName); +export const BlueskyPrivateKeyCookie = GetCookie(BlueskyPrivateKeyName); +export const BlueskyNonceCookie = GetCookie(BlueskyNonceName); +export const BlueskyAccessTokenCookie = GetCookie(BlueskyAccessTokenName); +export const BlueskyRefreshTokenCookie = GetCookie(BlueskyRefreshTokenName); + +// Tumblr +// None lmao. + +// FUNCTIONS +// Get the cookie from cookie storage. +export function GetCookie(CookieName = "") { + // Check if you put in nothing. + if (CookieName == 0) { + console.error("Where is the cookie name? Returning nothing..."); + return ""; + } + // Get the cookie. + let Cookie = document.cookie.split("; ").find((row) => row.startsWith(CookieName + "="))?.split("="); + // If the cookie doesn't exist... + if (Cookie == undefined || Cookie.length == 1) { + console.log("Cookie not found. Returning nothing..."); + return ""; + } else { + return Cookie[1]; + } +} + +// Check if a cookie is real (as in the value isn't nonexistant). +export function IsCookieReal(Cookie = "") { + if (Cookie.length == 0) { + return false; + } + return true; +} + +// Remove the cookie. +export function ExpireCookie(CookieName = "") { + document.cookie = CookieName + "=nothing;samesite=strict;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;"; + return; +} + +// Add a new cookie (or change it's value). +export function InputCookie(CookieName = "", Value = "") { + if (Value == 0 || CookieName == 0) { + console.error("You forgot to put in a value. Stopping..."); + return; + } + document.cookie = CookieName + "=" + Value + ";samesite=strict;path=/;expires=Fri, 31 Dec 9999 23:59:59 GMT;" + return; +} diff --git a/JS/MastodonAPI.js b/JS/MastodonAPI.js index 62a0920..95d20d7 100644 --- a/JS/MastodonAPI.js +++ b/JS/MastodonAPI.js @@ -1,8 +1,10 @@ +import * as Cookie from "./Cookies.js"; + export const Scopes = "read write follow push"; -let Origin = document.location.href; // Gets the public timeline. export async function GetPublicTimeline(Local = false, Remote = false, Website) { + // Cookies can be found in `setting.js` let Timeline; if (Local == true && Remote == true) { console.error("Don't set both Local and Remote timelines to true."); @@ -23,49 +25,56 @@ export async function GetPublicTimeline(Local = false, Remote = false, Website) } // Gets the favorites of a user that you have access to. -export async function GetFavorites(Website, MastodonAccessToken, MastodonTokenType) { +export async function GetFavorites() { let Favorites; // Check for a token. - if (document.cookie.split("; ").find((row) => row.startsWith(MastodonAccessToken + "="))?.split("=").length > 1) { + if (Cookie.IsCookieReal(Cookie.MastodonAccessTokenCookie)) { + let Website = Cookie.MastodonWebsiteCookie; // 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}}) + Favorites = await fetch(Website + "/api/v1/favourites", {method: "GET", headers: {"Authorization": Cookie.MastodonTokenTypeCookie + " " + Cookie.MastodonAccessTokenCookie}}) .then((response) => response.json()); } return Favorites; } // Gets the bookmarks of a user that you have access to. -export async function GetBookmarks(Website, MastodonAccessToken, MastodonTokenType) { +export async function GetBookmarks() { let Bookmarks; // Check for a token. - if (document.cookie.split("; ").find((row) => row.startsWith(MastodonAccessToken + "="))?.split("=").length > 1) { + if (Cookie.IsCookieReal(Cookie.MastodonAccessTokenCookie)) { + let Website = Cookie.MastodonWebsiteCookie; // 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}}) + Bookmarks = await fetch(Website + "/api/v1/bookmarks", {method: "GET", headers: {"Authorization": Cookie.MastodonTokenTypeCookie + " " + Cookie.MastodonAccessTokenCookie}}) .then((response) => response.json()); } return Bookmarks; } // Gets the notifications of a user that you have access to. -export async function GetNotifications(Website, MastodonAccessToken, MastodonTokenType) { +export async function GetNotifications() { 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}}) + if (Cookie.IsCookieReal(Cookie.MastodonAccessTokenCookie)) { + let Website = Cookie.MastodonWebsiteCookie; + // Get the varaibles that are stored in cookies and then input it. + Notifications = await fetch(Website + "/api/v1/notifications", {method: "GET", headers: {"Authorization": Cookie.MastodonTokenTypeCookie + " " + Cookie.MastodonAccessTokenCookie}}) .then((response) => response.json()); } return Notifications; } +// Make a status +export async function CreateStatus(Text) { + // Check for a token + if (Cookie.IsCookieReal(Cookie.MastodonAccessTokenCookie)) { + let Website = Cookie.MastodonWebsiteCookie; + return await fetch(Website + "/api/v1/statuses?status=" + Text , {method: "POST", headers: {"Authorization": Cookie.MastodonTokenTypeCookie + " " + Cookie.MastodonAccessTokenCookie}}) + .then((response) => response.json()); + } +} + // The first step to using the app. -export async function HandleAuthentication(Website, CookieClientID, CookieClientSecret) { +export async function HandleAuthentication(Website) { // See if the user is smart enough to put https. let InstanceData = ""; // Quickly check to see if it has something before :// so it doesn't screw the link. @@ -74,28 +83,30 @@ export async function HandleAuthentication(Website, CookieClientID, CookieClient } else { Website = "https://" + Website; } + // Save the website + Cookie.InputCookie(Cookie.MastodonWebsiteName, Website); // Registering the app. - InstanceData = await fetch(Website + "/api/v1/apps?client_name=Channel Viewer&redirect_uris=" + Origin + "&scopes=" + Scopes, {method: "POST"}) + InstanceData = await fetch(Website + "/api/v1/apps?client_name=Channel Viewer&redirect_uris=" + document.location.href + "&scopes=" + Scopes, {method: "POST"}) .then((response) => response.json()); // Save the client stuff as cookies. - 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;"; + Cookie.InputCookie(Cookie.MastodonClientIDName, InstanceData.client_id); + Cookie.InputCookie(Cookie.MastodonClientSecretName, InstanceData.client_secret); // Now authenticate the app. - document.location.href = Website + "/oauth/authorize?client_id=" + InstanceData.client_id + "&redirect_uri=" + Origin + "&response_type=code&scope=" + Scopes; + document.location.href = Website + "/oauth/authorize?client_id=" + InstanceData.client_id + "&redirect_uri=" + document.location.href + "&response_type=code&scope=" + Scopes; } // This specific functino goes after HandleAuthentication for when login happens. -export async function GainToken(Website, CookieClientID, CookieClientSecret, CookieAccessToken, CookieTokenType) { +export async function GainToken() { // 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("=").length > 1) { + if (document.location.href.split("code=").length > 1 && Cookie.IsCookieReal(Cookie.MastodonClientIDCookie) && !(Cookie.IsCookieReal(Cookie.MastodonAccessTokenCookie))) { + // Get some vars. 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"}) + let Website = Cookie.MastodonWebsiteCookie; + // Authenticate. + let AuthenticationToken = await fetch(Website + "/oauth/token?client_id=" + Cookie.MastodonClientIDCookie + "&client_secret=" + Cookie.MastodonClientSecretCookie + "&redirect_uri=" + document.location.href + "&grant_type=authorization_code&code=" + code, {method: "POST"}) .then((response) => response.json()); - // Cookify These - 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;"; + // Cookify These. + Cookie.InputCookie(Cookie.MastodonAccessTokenName, AuthenticationToken.access_token); + Cookie.InputCookie(Cookie.MastodonTokenTypeName, AuthenticationToken.token_type); } } diff --git a/JS/TumblrAPI.js b/JS/TumblrAPI.js index e69de29..79a00a1 100644 --- a/JS/TumblrAPI.js +++ b/JS/TumblrAPI.js @@ -0,0 +1 @@ +import * as Cookie from "./Cookies.js"; diff --git a/JS/index.js b/JS/index.js index fee1ad9..aadf229 100644 --- a/JS/index.js +++ b/JS/index.js @@ -1,4 +1,7 @@ import * as MastodonAPI from "./MastodonAPI.js"; +import * as BlueskyAPI from "./BlueskyAPI.js"; +import * as TumblrAPI from "./TumblrAPI.js"; +import * as Cookie from "./Cookies.js"; // GLOBAL VARS // fuck you. I see why website developers use divs so fucking often. @@ -12,15 +15,13 @@ let BrowserHeight = window.innerHeight; let ArrowsButton = document.getElementsByClassName("Arrow"); let SettingButton = document.getElementsByClassName("Setting")[0]; let MailButton = document.getElementsByClassName("Mail")[0]; +let PostingButton = document.getElementsByClassName("Posting")[0]; // Sounds 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 -let MastodonWebsite = "https://wetdry.world"; - // Update a timer function UpdateTime() { let TimeNow = new Date(); @@ -104,12 +105,17 @@ ArrowsButton[0].onclick = (event) => { // MastodonAPI integration async function PosterContainerUpdate() { // Cookies for the public timelines - let LocalCookie = document.cookie.split("; ").find((row) => row.startsWith("Local="))?.split("=")[1]; - var LocalTrue = (LocalCookie === 'true'); - let RemoteCookie = document.cookie.split("; ").find((row) => row.startsWith("Remote="))?.split("=")[1]; - var RemoteTrue = (RemoteCookie === 'true'); + let LocalCookie = Cookie.GetCookie("Local"); + var LocalTrue = (LocalCookie === "true"); + let RemoteCookie = Cookie.GetCookie("Remote"); + var RemoteTrue = (RemoteCookie === "true"); + let Website = Cookie.MastodonWebsiteCookie; - let Timeline = await MastodonAPI.GetPublicTimeline(LocalTrue, RemoteTrue, MastodonWebsite); + if (!(Cookie.IsCookieReal(Cookie.MastodonWebsiteCookie))) { + Website = "https://wetdry.world"; + } + + let Timeline = await MastodonAPI.GetPublicTimeline(LocalTrue, RemoteTrue, Website); let Content = []; let Users = []; for (let i in Timeline) { @@ -128,7 +134,12 @@ SettingButton.onclick = (event) => { window.location.href = "./HTML/setting.html"; } -// Open the notifs, private message, favorites, bookmarks +// Open the notifs, private message, favorites, ... anything mail related! MailButton.onclick = (event) => { window.location.href = "./HTML/mail.html"; } + +// Open the posting area +PostingButton.onclick = (event) => { + window.location.href = "./HTML/post.html"; +} diff --git a/JS/mail.js b/JS/mail.js index 86f3f02..2ba773f 100644 --- a/JS/mail.js +++ b/JS/mail.js @@ -1,26 +1,11 @@ import * as MastodonAPI from "./MastodonAPI.js"; import * as BlueskyAPI from "./BlueskyAPI.js"; import * as TumblrAPI from "./TumblrAPI.js"; - -// 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 BlueskyPublicKey = "bluesky_public_key"; -let BlueskyNonce = "bluesky_nonce"; +import * as Cookie from "./Cookies.js"; // Below is the thing it populates if you login. async function PopulateFavorites() { - let Favorites = await MastodonAPI.GetFavorites(MastodonWebsite, MastodonAccessToken, MastodonTokenType); + let Favorites = await MastodonAPI.GetFavorites(); let FavoritesArea = document.getElementsByClassName("Favorites")[0]; // Populate the favorites area. @@ -31,7 +16,7 @@ async function PopulateFavorites() { } async function PopulateBookmarks() { - let Bookmarks = await MastodonAPI.GetBookmarks(MastodonWebsite, MastodonAccessToken, MastodonTokenType); + let Bookmarks = await MastodonAPI.GetBookmarks(); let BookmarksArea = document.getElementsByClassName("Bookmarks")[0]; // Populate the Bookmarks area. @@ -42,7 +27,7 @@ async function PopulateBookmarks() { } async function PopulateNotifications() { - let Notifications = await MastodonAPI.GetNotifications(MastodonWebsite, MastodonAccessToken, MastodonTokenType); + let Notifications = await MastodonAPI.GetNotifications(); let NotificationsArea = document.getElementsByClassName("Notifications")[0]; // Populate the Conversations area. diff --git a/JS/post.js b/JS/post.js new file mode 100644 index 0000000..cf772e9 --- /dev/null +++ b/JS/post.js @@ -0,0 +1,27 @@ +import * as MastodonAPI from "./MastodonAPI.js"; +import * as BlueskyAPI from "./BlueskyAPI.js"; +import * as TumblrAPI from "./TumblrAPI.js"; +import * as Cookie from "./Cookies.js"; + +// Elements. +let PostButton = document.getElementsByClassName("button")[0]; +let InputArea = document.getElementsByClassName("text")[0]; + +// Clicking the beeg POST button. +PostButton.onclick = (event) => { + Post(); +} + +async function Post() { + let Text = InputArea.value; + // Mastodon posting. + if (Cookie.IsCookieReal(Cookie.MastodonAccessTokenCookie)) { + MastodonAPI.CreateStatus(Text); + } + // Bluesky posting. + if (Cookie.IsCookieReal(Cookie.BlueskyAccessTokenCookie)) { + let DID = await BlueskyAPI.GetBlueskyDID("https://woodear.us-west.host.bsky.network", "crowdedgames.group"); + BlueskyAPI.CreatePost("https://woodear.us-west.host.bsky.network", DID.did, Text); + } + InputArea.value = ""; +} diff --git a/JS/setting.js b/JS/setting.js index 57d122b..8c1560e 100644 --- a/JS/setting.js +++ b/JS/setting.js @@ -1,6 +1,7 @@ import * as MastodonAPI from "./MastodonAPI.js"; import * as BlueskyAPI from "./BlueskyAPI.js"; import * as TumblrAPI from "./TumblrAPI.js"; +import * as Cookie from "./Cookies.js"; // Settings buttons let LocalButton = document.getElementsByClassName("Local")[0]; @@ -13,97 +14,88 @@ 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 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"; +// original link +let Origin = location.origin + "/HTML/setting.html" // Change weather the timelines are public or remote LocalButton.onclick = (event) => { // Toggle the cookie - if (document.cookie.split(";").some((item) => item.trim().startsWith("Local="))) { - document.cookie = "Local=true;samesite=strict;path=/;expires=expires=0000-01-01;"; + if (Cookie.IsCookieReal(Cookie.GetCookie("Local"))) { + Cookie.ExpireCookie("Local"); } else { - document.cookie = "Local=true;samesite=strict;path=/;"; + Cookie.InputCookie("Local", "true"); } } RemoteButton.onclick = (event) => { // Toggle the cookie - if (document.cookie.split(";").some((item) => item.trim().startsWith("Remote="))) { - document.cookie = "Remote=true;samesite=strict;path=/;expires=expires=0000-01-01;"; + if (Cookie.IsCookieReal(Cookie.GetCookie("Remote"))) { + Cookie.ExpireCookie("Remote"); } else { - document.cookie = "Remote=true;samesite=strict;path=/;"; + Cookie.InputCookie("Remote", "true"); } } // Mastodon buttons +// Login MastodonLoginButton.onclick = (event) => { - if (MastodonWebInput != "") { - MastodonAPI.HandleAuthentication(MastodonWebsite, MastodonClientID, MastodonClientSecret); + if (MastodonWebInput.value != "") { + let text = MastodonWebInput.value + MastodonAPI.HandleAuthentication(text); } } +// Logout 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; + Cookie.ExpireCookie(Cookie.MastodonClientIDName); + Cookie.ExpireCookie(Cookie.MastodonClientSecretName); + Cookie.ExpireCookie(Cookie.MastodonAccessTokenName); + Cookie.ExpireCookie(Cookie.MastodonTokenTypeName); + document.location.href = Origin; } // Bluesky Buttons +// Login BlueskyLoginButton.onclick = (event) => { - if (BlueskyWebInput != "") { - BlueskyAPI.HandleAuthorization(BlueskyPKCEverifer, BlueskyPKCEchallenge, BlueskyNonce); + if (BlueskyWebInput.value != "") { + BlueskyAPI.HandleAuthorization(); } } +// Logout 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; + Cookie.ExpireCookie(Cookie.BlueskyPKCEVeriferName); + Cookie.ExpireCookie(Cookie.BlueskyPKCEChallengeName); + Cookie.ExpireCookie(Cookie.BlueskyNonceName); + Cookie.ExpireCookie(Cookie.BlueskyAccessTokenName); + Cookie.ExpireCookie(Cookie.BlueskyRefreshTokenName); + Cookie.ExpireCookie(Cookie.BlueskyPublicKeyName); + Cookie.ExpireCookie(Cookie.BlueskyPrivateKeyName); + document.location.href = Origin; } // if an access token is found, login. -function CheckLogin() { +async 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 (Cookie.IsCookieReal(Cookie.MastodonAccessTokenCookie)) { // Swap the buttons MastodonLoginButton.remove(); MastodonWebInput.remove(); MastodonLogoutButton.setAttribute("style", ""); } else { // Auto log in - MastodonAPI.GainToken(MastodonWebsite, MastodonClientID, MastodonClientSecret, MastodonAccessToken, MastodonTokenType); + await MastodonAPI.GainToken(Cookie.MastodonWebsiteName); } - 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) { + // Check for a bluesky token. + if (Cookie.IsCookieReal(Cookie.BlueskyAccessTokenCookie)) { // Swap the buttons BlueskyLoginButton.remove(); BlueskyWebInput.remove(); BlueskyLogoutButton.setAttribute("style", ""); } else { // Auto log in - BlueskyAPI.GainTokens(BlueskyPKCEverifer, BlueskyNonce, BlueskyAccessToken, BlueskyRefreshToken); + await BlueskyAPI.GainTokens(); } - // Check for a bluesky token. } // Runs on website start. diff --git a/README.md b/README.md index a500a8b..73d8218 100644 --- a/README.md +++ b/README.md @@ -9,3 +9,9 @@ A frontend to accessing my account on the fediverse - 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` + +## FAQ + +|Question|Answer| +|--------|-------| +|Where can I find my PDS?|Create an access token and then examine the payload's `aud`| diff --git a/index.html b/index.html index d4fdf5b..0489f4c 100644 --- a/index.html +++ b/index.html @@ -292,6 +292,7 @@