diff --git a/README.md b/README.md index a83c762..447807d 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,37 @@ Once you've installed dependencies, start a development server: ```bash npm run dev ``` -> You can preview the built app with `npm run preview`, regardless of whether you installed an adapter. Note that I don't use this in production, I build with adapter-vercel +> You can preview the built app with `npm run preview`. + +# ScratchLight +This repository also includes the code for the ScratchLight authentication system, which Scratchinfo uses. + +To use ScratchLight, just send your user to `/scratchlight/authpage?redirect=` with the `redirect` URL parameter being the Base64 encoded version of the page you want to direct your user to after they have authenticated, **excluding** the protocol and `://` which is assumed to be `https://`. + +You also will need to add the `username` URL parameter with the value being the username of the user you would like to authenticate. + +Then the user will be returned to your site with a `verifyCode` URL parameter. + +Then once your user has returned to your site, `POST` to `/scratchlight/verify` with a request like this: +```json +{ + "code": "Your verifyCode URL parameter" +} +``` +ScratchLight will either return two responses: one for success, and one for failure. I recommend verifying the authentication status on the server, so that you can issue a JWT/session cookie. + +Success: +```json +{ + "isError": false, + "username": "god286" +} +``` + +Failure: +```json +{ + "isError": true +} +``` +**After you have requested the `verifyCode` of your user, their authentication will be removed from our database.** \ No newline at end of file diff --git a/src/lib/bta.ts b/src/lib/bta.ts index 91fde50..883b8a8 100644 --- a/src/lib/bta.ts +++ b/src/lib/bta.ts @@ -1,5 +1,6 @@ // Thanks to https://gist.github.com/oeon/0ada0457194ebf70ec2428900ba76255 for the code! -const b2a = (a) => { +// Part of the code has been modified for TypeScript. +const b2a = (a): string => { var c, d, e, @@ -32,4 +33,17 @@ const b2a = (a) => { (o ? m.slice(0, o - 3) : m) + "===".slice(o || 3) ); }; -export default b2a; +function a2b(a): string { + var b, c, d, e = {}, f = 0, g = 0, h = "", i = String.fromCharCode, j = a.length; + for (b = 0; 64 > b; b++) e["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(b)] = b; + for (c = 0; j > c; c++) for (b = e[a.charAt(c)], f = (f << 6) + b, g += 6; g >= 8; ) ((d = 255 & f >>> (g -= 8)) || j - 2 > c) && (h += i(d)); + return h; +}; +function multi(decode, value): string { + if (decode) { + return a2b(value) + } else { + return b2a(value) + } +} +export default multi; diff --git a/src/routes/__error.svelte b/src/routes/__error.svelte new file mode 100644 index 0000000..6634eb3 --- /dev/null +++ b/src/routes/__error.svelte @@ -0,0 +1,2 @@ +
An error has occurred. This page might not exist or may have moved.
\ No newline at end of file diff --git a/src/routes/dashboard.svelte b/src/routes/dashboard.svelte index e2a69cb..901dab1 100644 --- a/src/routes/dashboard.svelte +++ b/src/routes/dashboard.svelte @@ -35,7 +35,7 @@ } }; // CUD = current user data - const CUDFetch = await fetch("/you/supa/fsauth/getUserFromToken", { + const CUDFetch = await fetch("/you/supa/scratchlight/getUserToken", { method: "POST", body: JSON.stringify({ token: window.localStorage.getItem("authToken").toString(), diff --git a/src/routes/login.svelte b/src/routes/login.svelte index b6a3904..06aae1f 100644 --- a/src/routes/login.svelte +++ b/src/routes/login.svelte @@ -1,25 +1,34 @@ -Your username will automatically be retrieved when you authenticate with FluffyScratch.
+
All Scratchinfo pages are hosted through
- When you are using Scratchinfo You, your Scratch account is verified by the FluffyScratch authentication platform. FluffyScratch allows Scratchinfo to verify your Scratch account.
+ When you are using Scratchinfo You, your Scratch account is verified by ScratchLight which is made by ScratchInfo. Anyone can see your ScratchInfo You page.
Our PostgreSQL database is provided by . All of the data used to provide Scratchinfo You is on Supabase.
- Scratchinfo is open source on our GitHub repository and is licensed under the MIT License.
Only include the code. No spaces or any characters other than the code will work.
ScratchLight Authentication
+
+ Please comment this code on the ScratchLight Authentication project, then
+ press on the "I'm Done" button.
+
+
+Go to Project
+I'm Done
+
+
diff --git a/src/routes/scratchlight/createAuthSession.ts b/src/routes/scratchlight/createAuthSession.ts
new file mode 100644
index 0000000..bafce72
--- /dev/null
+++ b/src/routes/scratchlight/createAuthSession.ts
@@ -0,0 +1,56 @@
+/**
+ * @type {import('@sveltejs/kit').RequestHandler}
+ */
+import { createClient } from "@supabase/supabase-js";
+import crypto from "crypto"
+import dotenv from "dotenv";
+dotenv.config();
+
+export async function post(request) {
+ try {
+ let parsedBody: any;
+ try {
+ parsedBody = JSON.parse(request.body);
+ } catch {
+ parsedBody = request.body;
+ }
+ if (typeof parsedBody.username == "undefined") {
+ return {
+ status: 500,
+ body: {
+ isError: true,
+ msg: "Please add a username in your POST request."
+ }
+ }
+ }
+ let postCode = crypto.randomBytes(128).toString('hex');
+ postCode = postCode.replace(/[0-9]/g, '');
+ const privateCode = crypto.randomBytes(24).toString('hex');
+ const supabase = createClient(
+ process.env["SCRATCHLIGHT_URL"],
+ process.env["SCRATCHLIGHT_KEY"]
+ )
+ const createSession = await supabase
+ .from("codes")
+ .insert([{ code: postCode, user: parsedBody.username, privateCode: privateCode }]);
+ if (createSession.error) {
+ throw new Error("Oh noes! An error has occurred!")
+ }
+ return {
+ body: {
+ code: postCode,
+ username: parsedBody.username,
+ private: privateCode
+ }
+ }
+ } catch (err) {
+ console.error(err);
+ return {
+ status: 500,
+ body: {
+ isError: true,
+ msg: "An error has occurred."
+ }
+ }
+ }
+}
diff --git a/src/routes/scratchlight/verify.ts b/src/routes/scratchlight/verify.ts
new file mode 100644
index 0000000..a6d0376
--- /dev/null
+++ b/src/routes/scratchlight/verify.ts
@@ -0,0 +1,63 @@
+/**
+ * @type {import('@sveltejs/kit').RequestHandler}
+ */
+import { createClient } from "@supabase/supabase-js";
+import dotenv from "dotenv";
+dotenv.config();
+
+export async function post(request) {
+ try {
+ let parsedBody: any;
+ try {
+ parsedBody = JSON.parse(request.body);
+ } catch {
+ parsedBody = request.body;
+ }
+ const privateCode = parsedBody.privateCode;
+ if (typeof privateCode == "undefined") {
+ throw new Error("no private code")
+ }
+ const supabase = createClient(
+ process.env["SCRATCHLIGHT_URL"],
+ process.env["SCRATCHLIGHT_KEY"]
+ )
+ const getData = await supabase
+ .from('codes')
+ .select()
+ .eq("privateCode", privateCode)
+ if (getData.error || getData.data.length == 0) {
+ throw new Error("Cannot find session.")
+ }
+ const comments = await (await fetch("https://api.scratch.mit.edu/users/god286/projects/601968190/comments/?limit=15&offset=0")).json();
+ for (let index = 0; index < comments.length; index++) {
+ const comment = comments[index];
+ if (comment.content == getData.data[0]["code"] && comment.author.username.toLowerCase() == getData.data[0]["user"].toLowerCase()) {
+ const deleteAuthSession = await supabase
+ .from('codes')
+ .delete()
+ .eq("privateCode", privateCode);
+ return {
+ body: {
+ isError: false,
+ username: getData.data[0]["user"]
+ }
+ }
+ }
+ }
+ return {
+ status: 500,
+ body: {
+ isError: true,
+ msg: "We couldn't verify this auth session."
+ }
+ }
+ } catch {
+ return {
+ status: 500,
+ body: {
+ isError: true,
+ msg: "An error has occurred."
+ }
+ }
+ }
+}
diff --git a/src/routes/you/supa/fsauth/auth.ts b/src/routes/you/supa/fsauth/auth.ts
deleted file mode 100644
index a893389..0000000
--- a/src/routes/you/supa/fsauth/auth.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
- * @type {import('@sveltejs/kit').RequestHandler}
- */
-import jwt from "jsonwebtoken";
-// this part is because process.env sometimes breaks
-import dotenv from "dotenv";
-dotenv.config();
-export async function get({ query }) {
- try {
- const privateCode = query.get("privateCode");
- const fluffyFetch = await fetch(
- `https://fluffyscratch.hampton.pw/auth/verify/v2/${privateCode}`
- );
- const ffc = await fluffyFetch.json();
- if (ffc.valid == false) {
- throw new Error("Authentication not valid.");
- }
- const myJWT = await jwt.sign(
- { username: ffc.username },
- process.env["SUPABASE_JWT_SECRET"],
- { expiresIn: "2h", audience: "scratchinfo" }
- );
- return {
- body: {
- works: true,
- token: myJWT,
- },
- };
- } catch (err) {
- console.error(err)
- return {
- status: 500,
- body: {
- works: false,
- },
- };
- }
-}
diff --git a/src/routes/you/supa/fsauth/checkAuth.svelte b/src/routes/you/supa/fsauth/checkAuth.svelte
deleted file mode 100644
index 1babf72..0000000
--- a/src/routes/you/supa/fsauth/checkAuth.svelte
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
You should be redirected to the homepage automatically once you have authenticated with FluffyScratch.
-Status: {status}
\ No newline at end of file diff --git a/src/routes/you/supa/scratchlight/auth.ts b/src/routes/you/supa/scratchlight/auth.ts new file mode 100644 index 0000000..1e04f1d --- /dev/null +++ b/src/routes/you/supa/scratchlight/auth.ts @@ -0,0 +1,54 @@ +/** + * @type {import('@sveltejs/kit').RequestHandler} + */ +import jwt from "jsonwebtoken"; + +import dotenv from "dotenv"; +dotenv.config(); +export async function post(request) { + let parsedBody = undefined; + try { + parsedBody = JSON.parse(request.body) + } catch { + parsedBody = request.body + } + try { + const privateCode = parsedBody.privateCode; + let protocol = "https://" + if (request.host.startsWith("localhost:")) { + protocol = "http://" // on localhost dont expect https + } + const slFetch = await fetch( + `${protocol}${request.host}/scratchlight/verify`, + { + method: "POST", + body: JSON.stringify({ + privateCode: privateCode + }) + } + ); + const scratchlight = await slFetch.json(); + if (scratchlight.isError == true) { + throw new Error("Authentication not valid."); + } + const realUser = await (await fetch(`https://api.scratch.mit.edu/users/${scratchlight.username}`)).json() + const myJWT = await jwt.sign( + { username: realUser.username }, + process.env["SUPABASE_JWT_SECRET"], + { expiresIn: "2h", audience: "scratchinfo" } + ); + return { + body: { + works: true, + token: myJWT, + }, + }; + } catch (err) { + return { + status: 500, + body: { + works: false, + }, + }; + } +} diff --git a/src/routes/you/supa/fsauth/getUserFromToken.ts b/src/routes/you/supa/scratchlight/getUserToken.ts similarity index 61% rename from src/routes/you/supa/fsauth/getUserFromToken.ts rename to src/routes/you/supa/scratchlight/getUserToken.ts index 13c60f2..6483d93 100644 --- a/src/routes/you/supa/fsauth/getUserFromToken.ts +++ b/src/routes/you/supa/scratchlight/getUserToken.ts @@ -1,16 +1,23 @@ -/** - * @type {import('@sveltejs/kit').RequestHandler} - */ import jwt from "jsonwebtoken"; // this part is because process.env sometimes breaks import dotenv from "dotenv"; dotenv.config(); export async function post(request) { + let parsedBody = undefined; + try { + parsedBody = JSON.parse(request.body) + } catch { + parsedBody = request.body + } try { try { - const jwtContent = jwt.verify(JSON.parse(request.body).token, process.env["SUPABASE_JWT_SECRET"], { + const isJWTGood = jwt.verify(parsedBody.token, process.env["SUPABASE_JWT_SECRET"], { maxAge: "2h", }); + if (!isJWTGood) { + throw new Error("no.") + } + const jwtContent = jwt.decode(parsedBody.token); return { body: jwtContent } @@ -32,4 +39,4 @@ export async function post(request) { }, }; } -} +} \ No newline at end of file diff --git a/src/routes/you/supa/scratchlight/verify.svelte b/src/routes/you/supa/scratchlight/verify.svelte new file mode 100644 index 0000000..16fda91 --- /dev/null +++ b/src/routes/you/supa/scratchlight/verify.svelte @@ -0,0 +1,30 @@ + + +