Added authentication
This commit is contained in:
commit
fecd3db3bc
|
@ -0,0 +1,3 @@
|
|||
.idea/
|
||||
node_modules/
|
||||
bin/
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "spotify-local",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"bin": "src/index.js",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"build": "pkg package.json"
|
||||
},
|
||||
"pkg": {
|
||||
"scripts": "src/**/*.js",
|
||||
"targets": [
|
||||
"node18-linux-x64",
|
||||
"node18-macos-x64",
|
||||
"node18-win-x64"
|
||||
],
|
||||
"outputPath": "bin"
|
||||
},
|
||||
"author": "Ian Wijma",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@inquirer/prompts": "^3.2.0",
|
||||
"express": "^4.18.2",
|
||||
"nanoid": "^3.3.6",
|
||||
"pkg": "^5.8.1",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.8.10",
|
||||
"@types/yargs": "^17.0.29"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
const {findAvailablePort} = require("../utilities/findAvailablePort");
|
||||
const {input} = require("@inquirer/prompts");
|
||||
const querystring = require("querystring");
|
||||
const {nanoid} = require("nanoid");
|
||||
const express = require('express')
|
||||
const {setAuth, hasAuth, authFile} = require("../utilities/authentication");
|
||||
|
||||
exports.command = 'authenticate'
|
||||
exports.description = 'starts the Spotity authentication process'
|
||||
exports.builder = function (argv) {
|
||||
return argv.option('client-id')
|
||||
}
|
||||
;
|
||||
exports.handler = async function (argv) {
|
||||
if (hasAuth()) {
|
||||
console.log(`spotify.local is already authenticated, remove "${authFile}" is you want to log out.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const port = await findAvailablePort();
|
||||
|
||||
const authenticateRoute = 'authenticate';
|
||||
const authenticateUrl = `http://localhost:${port}/${authenticateRoute}`;
|
||||
|
||||
const tutorial = [
|
||||
'Visit https://developer.spotify.com/dashboard and click "Create app"',
|
||||
'Set the "App name" as "spotify.local"',
|
||||
'Set the "App description" as "My spotify.local"',
|
||||
'Set the "Website" as "https://code.tmp.dev/ian/spotify.local"',
|
||||
`Set the "Redirect URI" as "${authenticateUrl}"`,
|
||||
'Select under "Which API/SDKs are you planning to use?" the "Web API"',
|
||||
'Sign your life away by agreeing with the "Developer Terms of Service and Design Guidelines"',
|
||||
'Click on the "Save" button',
|
||||
'Copy the "Client ID" from the settings page',
|
||||
];
|
||||
|
||||
tutorial.forEach((string, index) => console.log(`${index+1} ${string}`));
|
||||
|
||||
const clientId = argv.clientId ?? await input({ message: `${tutorial.length}. Paste the clientId here` })
|
||||
const originalState = nanoid();
|
||||
const redirectUrl = new URL('https://accounts.spotify.com/authorize');
|
||||
redirectUrl.search = querystring.stringify({
|
||||
response_type: 'code',
|
||||
client_id: clientId,
|
||||
scope: [
|
||||
'user-library-read',
|
||||
'user-read-private',
|
||||
'user-library-modify',
|
||||
'user-follow-modify',
|
||||
'user-follow-read',
|
||||
'playlist-read-private',
|
||||
'playlist-modify-public',
|
||||
'playlist-modify-private',
|
||||
'playlist-read-collaborative',
|
||||
'user-top-read',
|
||||
'user-read-recently-played',
|
||||
'user-read-playback-state',
|
||||
'user-modify-playback-state',
|
||||
'user-read-currently-playing',
|
||||
'user-read-playback-position'
|
||||
].join(' '),
|
||||
redirect_uri: authenticateUrl,
|
||||
state: originalState
|
||||
});
|
||||
|
||||
const app = express();
|
||||
|
||||
let server;
|
||||
app.get(`/${authenticateRoute}`, (req, res) => {
|
||||
const {state, code} = req.query;
|
||||
if (originalState === state) {
|
||||
setAuth(code);
|
||||
|
||||
res.send('<script>(() => close())()</script>');
|
||||
|
||||
server?.close();
|
||||
|
||||
console.log(`${tutorial.length+2}. Spotify.local has been authenticated`);
|
||||
} else {
|
||||
res.status(400);
|
||||
res.send('NO')
|
||||
}
|
||||
});
|
||||
|
||||
server = app.listen(port, () => console.log(`${tutorial.length+1}. Follow the following URL to authenticate: ${redirectUrl}`));
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
const Yargs = require('yargs');
|
||||
|
||||
Yargs(process.argv.splice(2))
|
||||
.commandDir('commands')
|
||||
.demandCommand()
|
||||
.help()
|
||||
.argv;
|
|
@ -0,0 +1,16 @@
|
|||
const { existsSync, mkdirSync, writeFileSync, readFileSync } = require('fs');
|
||||
const { homedir } = require('os');
|
||||
|
||||
const configDir = `${homedir()}/.spotify-local`
|
||||
const authenticationFile = `${configDir}/authentication`
|
||||
|
||||
const ensure = () => {
|
||||
if (!existsSync(configDir)) mkdirSync(configDir);
|
||||
if (!existsSync(authenticationFile)) writeFileSync(authenticationFile, '');
|
||||
return true;
|
||||
};
|
||||
|
||||
exports.authFile = authenticationFile;
|
||||
exports.setAuth = (code) => ensure() && writeFileSync(authenticationFile, code);
|
||||
exports.getAuth = () => ensure() && readFileSync(authenticationFile).toString();
|
||||
exports.hasAuth = () => ensure() && readFileSync(authenticationFile).toString() !== '';
|
|
@ -0,0 +1,36 @@
|
|||
const net = require('net');
|
||||
|
||||
const startPort = 5000;
|
||||
const endPort = 10000;
|
||||
|
||||
const portAvailable = (port) => new Promise((resolve, reject) => {
|
||||
const server = net.createServer();
|
||||
|
||||
server.once('error', function(err) {
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
reject([null, false])
|
||||
}
|
||||
|
||||
reject([err])
|
||||
});
|
||||
|
||||
server.once('listening', function() {
|
||||
server.close();
|
||||
resolve([null, true])
|
||||
});
|
||||
|
||||
console.log(`Checking port ${port}`);
|
||||
|
||||
server.listen(port);
|
||||
});
|
||||
|
||||
/**
|
||||
* @returns {Promise<number>}
|
||||
*/
|
||||
exports.findAvailablePort = async function () {
|
||||
for (let port = startPort; startPort <= endPort; port++) {
|
||||
const [error, available] = await portAvailable(port);
|
||||
if (error) throw error;
|
||||
if (available) return port;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue