diff --git a/extensions/LoginNexus.js b/extensions/LoginNexus.js new file mode 100644 index 0000000000..134e3e29ef --- /dev/null +++ b/extensions/LoginNexus.js @@ -0,0 +1,284 @@ +// Name: LoginNexus +// ID: loginNexus +// Description: A API-based authentication and registration extension +// By: Thebloxers998 +// License: MPL-2.0 + +(function(Scratch) { + 'use strict'; + + class LoginNexusExtension { + constructor() { + this.clientId = ''; + this.redirectUri = ''; + this.apiUri = ''; + this.authenticatedUsers = new Set(); + this.registeredUsers = new Set(); + } + + getInfo() { + return { + id: 'loginNexus', + name: 'LoginNexus', + color1: '#ADD8E6', + color2: '#87CEEB', + color3: '#B0E0E6', + blocks: [ + { + blockType: Scratch.BlockType.LABEL, + text: 'Settings' + }, + { + opcode: 'setClientId', + blockType: Scratch.BlockType.COMMAND, + text: 'set client ID to [CLIENT_ID]', + arguments: { + CLIENT_ID: { + type: Scratch.ArgumentType.STRING, + defaultValue: 'your-client-id' + } + } + }, + { + opcode: 'setRedirectUri', + blockType: Scratch.BlockType.COMMAND, + text: 'set redirect URI to [REDIRECT_URI]', + arguments: { + REDIRECT_URI: { + type: Scratch.ArgumentType.STRING, + defaultValue: 'your-redirect-uri' + } + } + }, + { + opcode: 'setApiUrl', + blockType: Scratch.BlockType.COMMAND, + text: 'set API URL to [API_URL]', + arguments: { + API_URL: { + type: Scratch.ArgumentType.STRING, + defaultValue: 'YOUR-API-KEY + } + } + }, + { + blockType: Scratch.BlockType.LABEL, + text: 'Authentication & Registration' + }, + { + opcode: 'registerUser', + blockType: Scratch.BlockType.COMMAND, + text: 'register user [USERNAME] with password [PASSWORD] ', + arguments: { + USERNAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: 'username' + }, + PASSWORD: { + type: Scratch.ArgumentType.STRING, + defaultValue: 'password' + } + } + }, + { + opcode: 'loginUser', + blockType: Scratch.BlockType.COMMAND, + text: 'login user [USERNAME] with password [PASSWORD]', + arguments: { + USERNAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: 'username' + }, + PASSWORD: { + type: Scratch.ArgumentType.STRING, + defaultValue: 'password' + } + } + }, + { + opcode: 'isUserStatus', + blockType: Scratch.BlockType.BOOLEAN, + text: 'is user [USERNAME] [STATUS]?', + arguments: { + USERNAME: { + type: Scratch.ArgumentType.STRING, + defaultValue: 'username' + }, + STATUS: { + type: Scratch.ArgumentType.STRING, + menu: 'statusOptions', + defaultValue: 'authenticated' + } + } + }, + { + blockType: Scratch.BlockType.LABEL, + text: 'Services' + }, + { + opcode: 'useService', + blockType: Scratch.BlockType.COMMAND, + text: 'use [SERVICE] to [ACTION]', + arguments: { + SERVICE: { + type: Scratch.ArgumentType.STRING, + menu: 'services', + defaultValue: 'Google' + }, + ACTION: { + type: Scratch.ArgumentType.STRING, + menu: 'actions', + defaultValue: 'Register' + } + } + }, + { + blockType: Scratch.BlockType.LABEL, + text: 'Debugging' + }, + { + opcode: 'debugMessage', + blockType: Scratch.BlockType.REPORTER, + text: 'last debug message' + } + ], + menus: { + services: { + acceptReporters: true, + items: ['Google', 'Microsoft'] + }, + actions: { + acceptReporters: true, + items: ['Register', 'Authenticate'] + }, + statusOptions: { + acceptReporters: true, + items: ['authenticated', 'registered'] + } + } + }; + } + + setDebugMessage(message) { + this.lastDebugMessage = message; + console.log(message); + } + + setClientId(args) { + this.clientId = args.CLIENT_ID; + this.setDebugMessage('Client ID set to: ' + this.clientId); + } + + setRedirectUri(args) { + this.redirectUri = args.REDIRECT_URI; + this.setDebugMessage('Redirect URI set to: ' + this.redirectUri); + } + + setApiUri(args) { + this.apiUri = args.API_URI; + this.setDebugMessage('API URI set to: ' + this.redirectUri); + } + + async registerUser(args) { + const username = args.USERNAME; + const password = args.PASSWORD; + + if (!username || !password) { + this.setDebugMessage('Invalid arguments provided'); + return; + } + + try { + const response = await Scratch.fetch(this.apiUrl , { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ username, password }) + }); + + const data = await response.json(); + if (response.ok) { + this.registeredUsers.add(username); + this.setDebugMessage('Registration successful: ' + JSON.stringify(data)); + } else { + this.setDebugMessage('Registration failed: ' + JSON.stringify(data)); + } + } catch (error) { + this.setDebugMessage('Error: ' + error.message); + } +} + + + + async loginUser(args) { + const username = args.USERNAME; + const password = args.PASSWORD; + + try { + const response = await Scratch.fetch(this.apiUrl); + if (!response.ok) { + throw new Error(`Network response was not ok: ${response.statusText}`); + } + const users = await response.json(); + + const user = users.find(u => u.username === username && u.password === password); + if (user) { + this.authenticatedUsers.add(username); + this.setDebugMessage(`Login successful for user: ${username}`); + } else { + this.setDebugMessage(`Login failed for user: ${username}`); + } + } catch (error) { + this.setDebugMessage(`Error: ${error.message}`); + } +} + + + + isUserStatus(args) { + const username = args.USERNAME; + const status = args.STATUS; + + if (!username || !status) { + this.setDebugMessage('Invalid arguments provided'); + return false; + } + + if (status === 'authenticated') { + return this.authenticatedUsers.has(username); + } else if (status === 'registered') { + return this.registeredUsers.has(username); + } + + return false; +} + + + useService(args) { + const service = args.SERVICE; + const action = args.ACTION; + + if (service === 'Google') { + if (action === 'Register' || action === 'Authenticate') { + this.setDebugMessage(`${action} with Google...`); + Scratch.openWindow(`https://accounts.google.com/o/oauth2/auth?client_id=${this.clientId}&redirect_uri=${this.redirectUri}&response_type=token&scope=email`, '_blank'); + } + } else if (service === 'Microsoft') { + if (action === 'Register' || action === 'Authenticate') { + this.setDebugMessage(`${action} with Microsoft...`); + Scratch.openWindow(`https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=${this.clientId}&response_type=token&redirect_uri=${this.redirectUri}&scope=openid email profile`, '_blank'); + } + } + } else { + this.setDebugMessage('Unknown service or action.'); + } + } + + debugMessage() { + return this.lastDebugMessage; + } + } + + Scratch.extensions.register(new LoginNexusExtension()); +})(Scratch); diff --git a/extensions/extensions.json b/extensions/extensions.json index 9027c00ac9..87bae01156 100644 --- a/extensions/extensions.json +++ b/extensions/extensions.json @@ -15,6 +15,7 @@ "bitwise", "Skyhigh173/bigint", "utilities", + "Thebloxers998/ServerAuth", "sound", "Lily/Video", "iframe", diff --git a/package-lock.json b/package-lock.json index 921fb49bbb..7abd790f5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,15 +15,15 @@ "adm-zip": "^0.5.16", "chokidar": "^4.0.1", "ejs": "^3.1.10", - "express": "^4.21.1", + "express": "^4.21.2", "image-size": "^1.1.1", "markdown-it": "^14.1.0" }, "devDependencies": { - "eslint": "^9.15.0", - "espree": "^9.6.1", + "eslint": "^9.16.0", + "espree": "^10.3.0", "esquery": "^1.6.0", - "prettier": "^3.3.3", + "prettier": "^3.4.2", "spdx-expression-parse": "^4.0.0" } }, @@ -106,40 +106,12 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", - "dev": true, - "dependencies": { - "acorn": "^8.14.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@eslint/js": { - "version": "9.15.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.15.0.tgz", - "integrity": "sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==", + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.16.0.tgz", + "integrity": "sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -653,17 +625,18 @@ } }, "node_modules/eslint": { - "version": "9.15.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.15.0.tgz", - "integrity": "sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==", + "version": "9.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.16.0.tgz", + "integrity": "sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.0", "@eslint/core": "^0.9.0", "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.15.0", + "@eslint/js": "9.16.0", "@eslint/plugin-kit": "^0.2.3", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -751,23 +724,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", - "dev": true, - "dependencies": { - "acorn": "^8.14.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/eslint/node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -781,17 +737,31 @@ } }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -848,9 +818,9 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -871,7 +841,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -886,6 +856,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/debug": { @@ -1565,9 +1539,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, "node_modules/prelude-ls": { "version": "1.2.1", @@ -1579,9 +1553,9 @@ } }, "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" diff --git a/package.json b/package.json index 07dcb94916..122140331e 100644 --- a/package.json +++ b/package.json @@ -27,15 +27,15 @@ "adm-zip": "^0.5.16", "chokidar": "^4.0.1", "ejs": "^3.1.10", - "express": "^4.21.1", + "express": "^4.21.2", "image-size": "^1.1.1", "markdown-it": "^14.1.0" }, "devDependencies": { - "eslint": "^9.15.0", - "espree": "^9.6.1", + "eslint": "^9.16.0", + "espree": "^10.3.0", "esquery": "^1.6.0", - "prettier": "^3.3.3", + "prettier": "^3.4.2", "spdx-expression-parse": "^4.0.0" }, "private": true