From c745572a7eb81fea3c1588a35af99e35964f0a26 Mon Sep 17 00:00:00 2001 From: knotteye Date: Thu, 30 Jul 2020 00:03:05 -0500 Subject: [PATCH 1/6] Update list of restricted usernames to avoid collision in api --- src/config.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/config.ts b/src/config.ts index f929fd9..27c0e31 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,6 +1,11 @@ import {parseAsYaml as parse} from "parse-yaml"; import {readFileSync as read} from "fs"; -var localconfig: Object = parse(read('config/config.yml')); +try { + var localconfig: Object = parse(read('config/config.yml')); +} catch (e) { + console.log('No config file found. Exiting.'); + process.exit(); +} const config: Object = { crypto: Object.assign({ saltRounds: 12 @@ -10,7 +15,7 @@ const config: Object = { domain: '', registration: false, email: null, - restrictedNames: [ 'live' ], + restrictedNames: [ 'live', 'user', 'users', 'register', 'login' ], rootredirect: '/users/live', version: process.env.npm_package_version, }, localconfig['satyr']), From db8d9dfe72f3e881948311b3ea292f2072eb8edc Mon Sep 17 00:00:00 2001 From: knotteye Date: Thu, 30 Jul 2020 00:45:08 -0500 Subject: [PATCH 2/6] Add API function for getting a user's configuration. --- docs/REST.md | 33 ++++++++++++++++++++------------- src/api.ts | 19 ++++++++++++++++++- src/http.ts | 37 +++++++++++++++++++++++++++++++------ 3 files changed, 69 insertions(+), 20 deletions(-) diff --git a/docs/REST.md b/docs/REST.md index 0c12b71..32087f8 100644 --- a/docs/REST.md +++ b/docs/REST.md @@ -51,22 +51,24 @@ play from: rtmp://example.com/live/username or https://example.com/live/username ### /api/users/live/ +UNFINISHED + Returns the usernames and stream titles of 10 users who are currently streaming Method: GET Authentication: no -Parameters: none +Parameters: num (optional), sort (optional) Response: Returns an array of objects containing the username and title of each stream. Returns an empty array if no one is streaming. Example: `[{username:"foo", title:"bar"}]` -### /api/users/live/:num +### /api/users/all -Same as above, with number indicated the number of users to list. Maximum of 50. +Same as above, but returns all users regardless of whether they are streaming. Also unfinished. @@ -140,7 +142,7 @@ Method: POST Authentication: yes -Paramters: A string array of the names of vods to be deleted. +Paramters: A string array of the names of vods to be deleted. (Do not include the file extension); Response: Returns `{error: "error code"}` or `{success: ""}` @@ -170,7 +172,7 @@ Parameters: A valid JWT cookie. No other parameters. Response: Returns `{error: "error code"}` or `{success: "new_stream_key"}` -### /api/:user/vods/list +### /api/:user/vods Get a list of the named users VODs @@ -178,7 +180,7 @@ Method: GET Authentication: no -Parameters: none +Parameters: user Response: Returns an array of VODs with the format `[{"name":"yote.mp4"},{"name":"yeet.mp4"}]` @@ -186,17 +188,22 @@ Notes: VODs are always available at http://domain.com/publicEndpoint/username/fi +## /api/:user/config -## Not Yet Implemented +Method: GET -#### /api/:user/info +Authentication: optional -List bio, stream title. If authenticated, list all settings. +Parameters: user -#### /api/user/streamkey/current +Response: Returns a JSON object with available information about the user. If the user is authenticated and searching for their own information, will return all available information. Otherwise it will return only the stream title and bio. In the case of searching for a user that does not exist, the returned object will contain only the username searched for. -Return current stream key +Example: `{username: "foo", title: "bar", about: "This is an example bio"}` -#### /api/users/live -Paging and sorting coming Soon(tm) \ No newline at end of file + +## Not Yet Implemented + +#### /api/user/streamkey/current + +Return current stream key \ No newline at end of file diff --git a/src/api.ts b/src/api.ts index a2bbb89..259dffd 100644 --- a/src/api.ts +++ b/src/api.ts @@ -69,4 +69,21 @@ async function deleteVODs(vodlist: Array, username: string): Promise{ + let t = {username: username}; + if(all) { + let users = await db.query('SELECT stream_key,record_flag FROM users WHERE username='+db.raw.escape(username)); + if(users[0]) Object.assign(t, users[0]); + let usermeta = await db.query('SELECT title,about FROM user_meta WHERE username='+db.raw.escape(username)); + if(usermeta[0]) Object.assign(t, users[0]); + let ci = await db.query('SELECT irc,xmpp,twitch,discord FROM chat_integration WHERE username='+db.raw.escape(username)); + if(ci[0]) Object.assign(t, ci[0]); + } + else { + let um = await db.query('SELECT title,about FROM user_meta WHERE username='+db.raw.escape(username)); + if(um[0]) Object.assign(t, um[0]); + } + return t; +} + +export { register, update, changepwd, changesk, login, updateChat, deleteVODs, getConfig }; \ No newline at end of file diff --git a/src/http.ts b/src/http.ts index 279d105..9cbdafb 100644 --- a/src/http.ts +++ b/src/http.ts @@ -168,15 +168,23 @@ async function initAPI() { ); }); app.get('/api/users/live', (req, res) => { + if(req.params.sort) { + + } + if(req.params.num){ + + } db.query('select username,title from user_meta where live=1 limit 10;').then((result) => { res.send(result); }); }); - app.get('/api/users/live/:num', (req, res) => { - if(req.params.num > 50) req.params.num = 50; - db.query('select username,title from user_meta where live=1 limit '+req.params.num+';').then((result) => { - res.send(result); - }); + app.get('/api/users/all', (req, res) => { + if(req.params.sort) { + + } + if(req.params.num) { + + } }); app.post('/api/register', (req, res) => { api.register(req.body.username, req.body.password, req.body.confirm).then( (result) => { @@ -312,7 +320,7 @@ async function initAPI() { }); } }); - app.get('/api/:user/vods/list', (req, res) => { + app.get('/api/:user/vods', (req, res) => { readdir('./site/live/'+req.params.user, {withFileTypes: true} , (err, files) => { if(err) { res.send([]); @@ -322,6 +330,23 @@ async function initAPI() { res.send(list); }); }); + app.get('/api/:user/config', (req, res) => { + if(req.cookies.Authorization) validToken(req.cookies.Authorization).then(r => { + if(r && r['username'] === req.params.user) { + api.getConfig(req.params.user, true).then(re => { + res.send(JSON.stringify(re)); + }); + return; + } + api.getConfig(req.params.user).then(re => { + res.send(JSON.stringify(re)); + }); + return; + }); + api.getConfig(req.params.user).then(r => { + res.send(JSON.stringify(r)); + }); + }); } async function initSite(openReg) { From df51432a8f62ac050f25c1ce333dfaa5491a2bdd Mon Sep 17 00:00:00 2001 From: knotteye Date: Thu, 30 Jul 2020 01:14:33 -0500 Subject: [PATCH 3/6] Fix /api/:user/config not returning all info for an authorized user. --- src/api.ts | 2 +- src/http.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api.ts b/src/api.ts index 259dffd..3883abe 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,4 +1,4 @@ -import * as db from "./database" +import * as db from "./database"; import { config } from "./config"; import {unlink} from "fs"; diff --git a/src/http.ts b/src/http.ts index 9cbdafb..14755a2 100644 --- a/src/http.ts +++ b/src/http.ts @@ -338,12 +338,12 @@ async function initAPI() { }); return; } - api.getConfig(req.params.user).then(re => { + else api.getConfig(req.params.user).then(re => { res.send(JSON.stringify(re)); }); return; }); - api.getConfig(req.params.user).then(r => { + else api.getConfig(req.params.user).then(r => { res.send(JSON.stringify(r)); }); }); From f7c7f0578629a622473ab265c13a708ce1dd3658 Mon Sep 17 00:00:00 2001 From: knotteye Date: Thu, 30 Jul 2020 01:34:22 -0500 Subject: [PATCH 4/6] Implement an API call for getting the current stream key. --- docs/REST.md | 18 ++++++++++++++---- src/http.ts | 13 +++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/docs/REST.md b/docs/REST.md index 32087f8..4218085 100644 --- a/docs/REST.md +++ b/docs/REST.md @@ -188,7 +188,9 @@ Notes: VODs are always available at http://domain.com/publicEndpoint/username/fi -## /api/:user/config +### /api/:user/config + +Get information about the specified user. Method: GET @@ -202,8 +204,16 @@ Example: `{username: "foo", title: "bar", about: "This is an example bio"}` -## Not Yet Implemented - #### /api/user/streamkey/current -Return current stream key \ No newline at end of file +Returns current stream key for the authenticated user. + +Method: GET + +Authentication: yes + +Parameters: none + +Response: returns a JSON object with the stream key + +Example: `{"stream_key": "abcdefghijklmno12345"}` or `{"error":"error reason"}` \ No newline at end of file diff --git a/src/http.ts b/src/http.ts index 14755a2..e1e20dd 100644 --- a/src/http.ts +++ b/src/http.ts @@ -274,6 +274,19 @@ async function initAPI() { } }); }); + app.get('/api/user/streamkey/current', (req, res) => { + validToken(req.cookies.Authorization).then((t) => { + if(t) { + db.query('SELECT stream_key FROM users WHERE username='+db.raw.escape(t['username'])).then(o => { + if(o[0]) res.send(o[0]); + else res.send('{"error":""}'); + }); + } + else { + res.send('{"error":"invalid token"}'); + } + }); + }); app.post('/api/user/streamkey', (req, res) => { validToken(req.cookies.Authorization).then((t) => { if(t) { From 09bf2a8ac1aa2a85e6eaaefea3c19ec92eec9d3f Mon Sep 17 00:00:00 2001 From: knotteye Date: Thu, 30 Jul 2020 02:49:02 -0500 Subject: [PATCH 5/6] Add paging and sorting to /api/users/live and /api/users/all calls --- src/http.ts | 57 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/src/http.ts b/src/http.ts index e1e20dd..3b122b8 100644 --- a/src/http.ts +++ b/src/http.ts @@ -167,24 +167,65 @@ async function initAPI() { }) ); }); - app.get('/api/users/live', (req, res) => { - if(req.params.sort) { + app.post('/api/users/live', (req, res) => { + let qs = 'SELECT username,title FROM user_meta WHERE live=1'; + if(req.body.sort) { + switch (req.body.sort) { + case "alphabet": + qs += ' ORDER BY username ASC'; + break; + case "alphabet-r": + qs += ' ORDER BY username DESC'; + break; + default: + break; + } } - if(req.params.num){ + if(!req.body.num || req.body.num > 50 || req.body.num === NaN || req.body.num === Infinity || typeof(req.body.num) !== 'number'){ + req.body.num = 10; } - db.query('select username,title from user_meta where live=1 limit 10;').then((result) => { - res.send(result); + qs += ' LIMIT '+req.body.num; + + if(req.body.page && typeof(req.body.page) === 'number' && req.body.page !== NaN && req.body.page !== Infinity){ + qs += ' OFFSET '+Math.floor(req.body.num * req.body.page); + } + + db.query(qs+';').then((result) => { + if(result) res.send(result); + else res.send('{"error":""}'); }); }); - app.get('/api/users/all', (req, res) => { - if(req.params.sort) { + app.post('/api/users/all', (req, res) => { + let qs = 'SELECT username,title FROM user_meta'; + if(req.body.sort) { + switch (req.body.sort) { + case "alphabet": + qs += ' ORDER BY username ASC'; + break; + case "alphabet-r": + qs += ' ORDER BY username DESC'; + break; + default: + break; + } + } + + if(!req.body.num || req.body.num > 50 || req.body.num === NaN || req.body.num === Infinity || typeof(req.body.num) !== 'number'){ + req.body.num = 10; } - if(req.params.num) { + qs += ' LIMIT '+req.body.num; + if(req.body.page && typeof(req.body.page) === 'number' && req.body.page !== NaN && req.body.page !== Infinity){ + qs += ' OFFSET '+Math.floor(req.body.num * req.body.page); } + + db.query(qs+';').then((result) => { + if(result) res.send(result); + else res.send('{"error":""}'); + }); }); app.post('/api/register', (req, res) => { api.register(req.body.username, req.body.password, req.body.confirm).then( (result) => { From cbcd1d8ba8d8db1fbb06276562906ae7ed169683 Mon Sep 17 00:00:00 2001 From: knotteye Date: Thu, 30 Jul 2020 02:55:33 -0500 Subject: [PATCH 6/6] Document /api/users/live and /api/users/all and modify the documentation format --- docs/REST.md | 170 +++++++++++++++++++++++++++------------------------ 1 file changed, 91 insertions(+), 79 deletions(-) diff --git a/docs/REST.md b/docs/REST.md index 4218085..67bbf8b 100644 --- a/docs/REST.md +++ b/docs/REST.md @@ -1,34 +1,34 @@ -## Using Satyr's Rest API +# Using Satyr's Rest API -### /api/instance/info +## /api/instance/info Generic enformation about the instance. -Method: GET +**Method**: GET -Authentication: no +**Authentication**: no -Parameters: none +**Parameters**: none -Response: Returns a JSON object containing the name, domain, version, email, and whether registration is open. Email will be null if not specified. +**Response**: Returns a JSON object containing the name, domain, version, email, and whether registration is open. Email will be null if not specified. -Example: `{name: "Example Instance", domain: "example.com", registration: false, version: 0.7, email: null}` +**Example**: `{name: "Example Instance", domain: "example.com", registration: false, version: 0.7, email: null}` -### /api/instance/config +## /api/instance/config Configuration of the instance relating to media -Method: GET +**Method**: GET -Authentication: no +**Authentication**: no -Parameters: none +**Parameters**: none -Response: JSON object containing the port and ping_timeout for RTMP, public and private play endpoints, and whether adaptive livestreaming and VOD recording are enabled. +**Response**: JSON object containing the port and ping_timeout for RTMP, public and private play endpoints, and whether adaptive livestreaming and VOD recording are enabled. -Example: +**Example**: ``` { rtmp: { @@ -43,177 +43,189 @@ Example: } } ``` + Public and private endpoints work like this, from the above example: + stream to: rtmp://example.com/stream -play from: rtmp://example.com/live/username or https://example.com/live/username/index.mpd +play from: rtmp://example.com/live/username or https://example.com/live/username/index.mpd -### /api/users/live/ -UNFINISHED +## /api/users/live/ Returns the usernames and stream titles of 10 users who are currently streaming -Method: GET +**Method**: POST + +**Authentication**: no + +**Parameters**: num (optional), sort (optional), page (optional) -Authentication: no +number is the number of results (maximum is 50) -Parameters: num (optional), sort (optional) +sort is the optional way to sort results. current options are "alphabet" and "alphabet-r" (reversed) -Response: Returns an array of objects containing the username and title of each stream. Returns an empty array if no one is streaming. +page is the page number (i.e. skip the first num * page results) -Example: `[{username:"foo", title:"bar"}]` +**Response**: Returns an array of objects containing the username and title of each stream. Returns an empty array if no one is streaming. Returns `{"error":"error code"}` in the event of an error. Will attempt to correct malformed requests to default values. +**Example**: `[{username:"foo", title:"bar"}]` -### /api/users/all + +## /api/users/all Same as above, but returns all users regardless of whether they are streaming. Also unfinished. -### /api/register +## /api/register Register a new user. -Method: POST +**Method**: POST -Authentication: no +**Authentication**: no -Parameters: Username, password, confirm +**Parameters**: Username, password, confirm -Response: If successful, returns a json object with the users stream key. Otherwise returns `{error: "error reason"}` +**Response**: If successful, returns a json object with the users stream key. Otherwise returns `{error: "error reason"}` -Examples: +**Examples**: `{stream_key: "asdfghjkltyuiop12345"}` + `{error: "registration disabled"}` -### /api/login +## /api/login Obtain a signed json web token for authentication -Method: POST +**Method**: POST -Authentication: no +**Authentication**: no -Parameters: Username and password OR a valid JWT cookie expiring in less than 24 hours +**Parameters**: Username and password OR a valid JWT cookie expiring in less than 24 hours -Response: If succesful, will return `{success: ""}` or `{success: "already verified"}` if the JWT provided is too early to be renewed. If unsuccesful, will return `{error: "invalid password"}` or `{error: "Username or Password Incorrect"}` depending on the authentication method. Note that if a JWT is available, the parameters will be ignored. +**Response**: If succesful, will return `{success: ""}` or `{success: "already verified"}` if the JWT provided is too early to be renewed. If unsuccesful, will return `{error: "invalid password"}` or `{error: "Username or Password Incorrect"}` depending on the authentication method. Note that if a JWT is available, the parameters will be ignored. -Notes: I've already listed nearly every response. My final note is that the JWT is set as the cookie 'Authorization', not returned in the response. +**Notes**: I've already listed nearly every response. My final note is that the JWT is set as the cookie 'Authorization', not returned in the response. -### /api/user/update +## /api/user/update Update the current user's information -Method: POST +**Method**: POST + +**Authentication**: yes -Authentication: yes +**Parameters**: title, bio, rec -Parameters: title, bio, rec Rec is a boolean (whether to record VODs), others are strings. Parameters that are not included in the request will not be updated. -Response: Returns `{error: "error code"}` or `{success: ""}` +**Response**: Returns `{error: "error code"}` or `{success: ""}` -### /api/user/update/chat +## /api/user/update/chat Update the chatrooms on other platforms to integrate with the user's stream chat -Method: POST +**Method**: POST -Authentication: yes +**Authentication**: yes -Parameters: discord, xmpp, twitch irc -All strings corresponding to a channel name to mirror to. XMPP is currently unused. Parameters not included in the request will not be updated. +**Parameters**: discord, xmpp, twitch irc -Response: Returns `{error: "error code"}` or `{success: ""}` +Strings corresponding to a channel name to mirror to. XMPP is currently unused. Parameters not included in the request will not be updated. +**Response**: Returns `{error: "error code"}` or `{success: ""}` -### /api/user/vods/delete + +## /api/user/vods/delete Delete the specified vods of the current user -Method: POST +**Method:** POST -Authentication: yes +**Authentication**: yes -Paramters: A string array of the names of vods to be deleted. (Do not include the file extension); +**Paramters**: A string array of the names of vods to be deleted. (Do not include the file extension); -Response: Returns `{error: "error code"}` or `{success: ""}` +**Response**: Returns `{error: "error code"}` or `{success: ""}` -### /api/user/password +## /api/user/password Change the current user's password -Method: POST +**Method**: POST -Authentication: yes +**Authentication**: yes -Parameters: The user's current password, the new password, AND a valid JWT cookie. +**Parameters**: The user's current password, the new password, AND a valid JWT cookie. -Response: Returns `{error: "error code"}` or `{success: ""}` +**Response**: Returns `{error: "error code"}` or `{success: ""}` -### /api/user/streamkey +## /api/user/streamkey Change the current user's stream key. This will not affect the stream if the user is currently live. -Method: POST +**Method**: POST + +**Authentication**: yes + +**Parameters**: A valid JWT cookie. No other parameters. -Authentication: yes +**Response**: Returns `{error: "error code"}` or `{success: "new_stream_key"}` -Parameters: A valid JWT cookie. No other parameters. -Response: Returns `{error: "error code"}` or `{success: "new_stream_key"}` -### /api/:user/vods +## /api/:user/vods Get a list of the named users VODs -Method: GET +**Method**: GET -Authentication: no +**Authentication**: no -Parameters: user +**Parameters**: user -Response: Returns an array of VODs with the format `[{"name":"yote.mp4"},{"name":"yeet.mp4"}]` +**Response**: Returns an array of VODs with the format `[{"name":"yote.mp4"},{"name":"yeet.mp4"}]` -Notes: VODs are always available at http://domain.com/publicEndpoint/username/filename.mp4 +**Notes**: VODs are always available at http://domain.com/publicEndpoint/username/filename.mp4 -### /api/:user/config +## /api/:user/config Get information about the specified user. -Method: GET +**Method**: GET -Authentication: optional +**Authentication**: optional -Parameters: user +**Parameters**: user -Response: Returns a JSON object with available information about the user. If the user is authenticated and searching for their own information, will return all available information. Otherwise it will return only the stream title and bio. In the case of searching for a user that does not exist, the returned object will contain only the username searched for. +**Response**: Returns a JSON object with available information about the user. If the user is authenticated and searching for their own information, will return all available information. Otherwise it will return only the stream title and bio. In the case of searching for a user that does not exist, the returned object will contain only the username searched for. -Example: `{username: "foo", title: "bar", about: "This is an example bio"}` +**Example**: `{username: "foo", title: "bar", about: "This is an example bio"}` -#### /api/user/streamkey/current +## /api/user/streamkey/current -Returns current stream key for the authenticated user. +Returns the current stream key for the authenticated user. -Method: GET +**Method**: GET -Authentication: yes +**Authentication**: yes -Parameters: none +**Parameters**: none -Response: returns a JSON object with the stream key +**Response**: returns a JSON object with the stream key -Example: `{"stream_key": "abcdefghijklmno12345"}` or `{"error":"error reason"}` \ No newline at end of file +**Example**: `{"stream_key": "abcdefghijklmno12345"}` or `{"error":"error reason"}` \ No newline at end of file