-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #76 from echo-lab/develop
Admin control, user security
- Loading branch information
Showing
14 changed files
with
460 additions
and
158 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,29 @@ | ||
# name of host server; format like 'http://localhost' (no port or slash) | ||
# hostname of backend server; format like 'http://localhost' (no port or slash) | ||
HOST_NAME= | ||
# port to listen on; set BACKEND_PORT to this in client/.env when developing with client/ $ npm start | ||
PORT= | ||
# when developing with client/ $ npm start, this is the PORT you set in client/.env; otherwise optional | ||
FRONTEND_PORT= | ||
|
||
# 'development' if developing with client/ $ npm start, 'production' otherwise | ||
NODE_ENV= | ||
# required if NODE_ENV=development, ignored otherwise; this is the hostname:port | ||
# serving the frontend; port is the PORT you set in client/.env; e.g. 'http://localhost:8888' | ||
FRONTEND_ADDRESS= | ||
|
||
# from spotify developer dashboard | ||
CLIENT_ID= | ||
# from spotify developer dashboard | ||
CLIENT_SECRET= | ||
|
||
# spotify account id of account that owns all the playlists | ||
OWNER_ACCOUNT_ID= | ||
# refresh token for the owner account | ||
OWNER_ACCOUNT_REFRESH_TOKEN= | ||
# passcode for making admin requests to server, must include in cookie 'admin_key' | ||
ADMIN_KEY= | ||
|
||
# path to playlist and user ids csv, usually starts with 'db/...' | ||
DB_IDS= | ||
# path to playlists collection, usually starts with 'db/...' | ||
DB_PLAYLISTS= | ||
# 'development' or 'production' | ||
NODE_ENV= | ||
# for debugging auth flow, probably not needed (optional) | ||
API_TARGET= | ||
# path to users collection, usually starts with 'db/...' | ||
DB_USERS= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
# both of these are only needed when developing with (client) npm start | ||
# port to listen on; set FRONTEND_PORT to this in ../.env | ||
# port to listen on; use this for FRONTEND_ADDRESS in ../.env | ||
PORT= | ||
# this is the PORT you set in ../.env | ||
BACKEND_PORT= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# format of each line: | ||
# playlist_id,chat_mode,user_id1,user_id2,...# comments for convenience | ||
|
||
# chat_mode can be `situated`, `separate`, or `hybrid` | ||
# playlist_id and chat_mode are required; you can have 0 or more user_ids | ||
# trailing commas are ok | ||
# spaces are not ok | ||
|
||
# for convenience you could list (in comments!) the display names of all | ||
# the playlist ids and user ids here to help you keep track of them: | ||
# "Playlist name": playlist_id | ||
# "User name": user_id | ||
# remember the same user id can show up in multiple playlists if they're | ||
# involved in multiple groups | ||
|
||
# to remove a playlist / make a playlist inaccessible to all users, you MUST | ||
# still list the playlist here but remove all user_ids. if you leave out a | ||
# playlist it will be unaffected and still be accessible to users | ||
|
||
# line example if playlist id is js3n12p, mode is hybrid, user ids are p1428np, | ||
# 72k7l65, and grhj35a: | ||
# js3n12p,hybrid,p1428np,72k7l65,grhj35a# comment if needed |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
|
||
|
||
import { Application } from 'express' | ||
import { playlistsDB, usersDB } from './db' | ||
import { initializePlaylist } from './initializePlaylist' | ||
import { spotifyApi } from './ownerAccount' | ||
import { parseIdsCsv } from './parseIdsCsv' | ||
|
||
|
||
|
||
export const setupAdmin = (app: Application) => { | ||
|
||
/** | ||
* log admin requests | ||
*/ | ||
app.use('/admin/', (req, res, next) => { | ||
console.log(`${req.method} ${req.originalUrl} request`) | ||
next() | ||
}) | ||
|
||
|
||
|
||
/** | ||
* ensure admin requests have admin key | ||
*/ | ||
app.use('/admin/', (req, res, next) => { | ||
if (req.cookies.admin_key !== process.env.ADMIN_KEY) { | ||
res.sendStatus(403) | ||
return | ||
} | ||
next() | ||
}) | ||
|
||
|
||
/** | ||
* for testing curl, admin_key, etc | ||
*/ | ||
app.get('/admin/test', (req, res) => { | ||
res.sendStatus(200) | ||
}) | ||
|
||
|
||
|
||
|
||
/** | ||
* endpoint for telling server to read ids.csv and update the DBs | ||
*/ | ||
app.post('/admin/load-ids', async (req, res) => { | ||
try { | ||
const { byPlaylist, byUser } = await parseIdsCsv(process.env.DB_IDS) | ||
|
||
/** | ||
* for each playlist in configData: | ||
* - if playlistId found in db: | ||
* - set chatMode; all data inside is valid for all chatModes so it's not complicated | ||
* - set users array in playlist, push playlist for each user | ||
* - if user is new to playlist, do anything like a separate chat event? | ||
* - if user is removed from playlist, do anything? TODO ask team what should be done in frontend | ||
* - considering this bc of possible future features like listing names, color coding | ||
* - activeUsers and archivedUsers arrays? | ||
* - else: | ||
* - initializePlaylist | ||
* - remove call from get playlist endpoint | ||
* - in playlist endpoint, if dbPlaylist or spotifyPlaylist not found, error | ||
* TODO make the promises parallel | ||
*/ | ||
for (const config of byPlaylist) { | ||
const dbPlaylist = await playlistsDB.findOne({ _id: config.playlistId }) | ||
if (dbPlaylist) { | ||
// playlistId found in db | ||
// TODO to do things with new/removed users, compare config.userIds to | ||
// dbPlaylist.users here | ||
await playlistsDB.update({ _id: config.playlistId }, { | ||
$set: { | ||
chatMode: config.chatMode, | ||
users: config.userIds, | ||
} | ||
}) | ||
} else { | ||
// playlist must exist in spotify, otherwise there's an error | ||
await initializePlaylist( | ||
(await spotifyApi.getPlaylist(config.playlistId)).body, | ||
config | ||
) | ||
|
||
// for testing with fake playlist ids, replace above line with below: | ||
// await initializePlaylist({ tracks: { items: [ | ||
// { track: { id: 'mock track' }, added_by: { id: 'mock user' } } | ||
// ]}} as SpotifyApi.SinglePlaylistResponse, config) | ||
} | ||
} | ||
|
||
// fetch all users currently in db | ||
const users = await usersDB.find({}) | ||
|
||
// a user can either be in the config but not the db, in the db but not | ||
// the config, or in both | ||
|
||
// for each user in the config, either set playlists or insert into db | ||
for (const [userId, playlists] of byUser) { | ||
// not using the upsert feature of nedb because i don't know how | ||
// reliable it is with promisify | ||
if (users.filter(user => user._id === userId).length) { | ||
// the user exists in the db | ||
await usersDB.update({ _id: userId }, { $set: { playlists } }) | ||
} else { | ||
await usersDB.insert({ _id: userId, playlists }) | ||
} | ||
} | ||
|
||
// we missed all the db users who aren't mentioned in the config | ||
for (const { _id: userId } of users) { | ||
// for all users, if they were not listed in the config then set | ||
// playlists to [] | ||
if (!byUser.has(userId)) { | ||
await usersDB.update({ _id: userId }, { $set: { playlists: [] }}) | ||
} | ||
} | ||
|
||
// manually stringify json to make spacing human readable | ||
res.type('application/json') | ||
res.send(JSON.stringify({byPlaylist, byUser: [...byUser]}, null, 2)) | ||
|
||
} catch (e) { | ||
console.error(e) | ||
if (e.type === 'invalid chatMode') { | ||
res.status(400).send(e.error.message) | ||
} else if (e.code === 'ENOENT') { | ||
// don't naively send e.error.message, which has the full file path | ||
res.send(`File ${process.env.DB_IDS} not found`) | ||
} else { | ||
res.end() | ||
} | ||
} | ||
}) | ||
|
||
|
||
|
||
/** | ||
* catch all other admin requests | ||
*/ | ||
app.all(['/admin', '/admin/*'], (req, res) => { | ||
console.log(`${req.path} not found`) | ||
res.sendStatus(404) | ||
}) | ||
|
||
|
||
} | ||
|
||
|
Oops, something went wrong.