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