Big Commit!

Seriously, this one is pretty massive. Satyr now has proper sessions in the browser (like a real website), and a lot of changes were made.

API Endpoints were changed from requiring a username and password to requiring a valid JsonWebToken, obtained from /api/login
Satyr will generate a PEM format key for JWT signing and verification on startup if it can't find one at config/jwt.pem
This file was added to .gitignore
Two new depencies: cookie-parser and jose, for reading and signing JWTs.

Refactored http.ts into mutiple functions, with a couple helper functions related to cookies and JWT decoding and verification. Socket.IO chat will also automatically log in users with a valid JWT.

Refactor api.ts to reflect new requirements from endpoints.

Minor bugfix in server.ts so we don't throw an uncaught exception when rejecting a stream with an invalid key.

Transcode options readded to default.toml. They do nothing and they are not sane defaults. Both of those things are in the todo list.
This commit is contained in:
knotteye 2019-12-03 19:51:14 -06:00
parent 31426a0c41
commit 25cf8a37a2
13 changed files with 378 additions and 121 deletions

1
.gitignore vendored
View File

@ -1,6 +1,7 @@
node_modules node_modules
site/live site/live
config/local.toml config/local.toml
config/jwt.pem
config/generated.toml config/generated.toml
install/db_setup.sql install/db_setup.sql
build/** build/**

View File

@ -51,3 +51,8 @@ record = false
publicEndpoint = 'live' publicEndpoint = 'live'
privateEndpoint = 'stream' privateEndpoint = 'stream'
ffmpeg = '' ffmpeg = ''
[transcode]
adapative = false
variants = 3
format = 'dash'

49
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "satyr", "name": "satyr",
"version": "0.4.3", "version": "0.4.4",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -121,6 +121,16 @@
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY="
}, },
"asn1.js": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.2.0.tgz",
"integrity": "sha512-Q7hnYGGNYbcmGrCPulXfkEw7oW7qjWeM4ZTALmgpuIcZLxyqqKYWxCZg2UBm8bklrnB4m2mGyJPWfoktdORD8A==",
"requires": {
"bn.js": "^4.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0"
}
},
"assign-symbols": { "assign-symbols": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
@ -257,6 +267,11 @@
"resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz",
"integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig=="
}, },
"bn.js": {
"version": "4.11.8",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
},
"body-parser": { "body-parser": {
"version": "1.19.0", "version": "1.19.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
@ -507,6 +522,22 @@
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
}, },
"cookie-parser": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.4.tgz",
"integrity": "sha512-lo13tqF3JEtFO7FyA49CqbhaFkskRJ0u/UAiINgrIXeRCY41c88/zxtrECl8AKH3B0hj9q10+h3Kt8I7KlW4tw==",
"requires": {
"cookie": "0.3.1",
"cookie-signature": "1.0.6"
},
"dependencies": {
"cookie": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
}
}
},
"cookie-signature": { "cookie-signature": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
@ -1837,6 +1868,14 @@
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
"optional": true "optional": true
}, },
"jose": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/jose/-/jose-1.15.1.tgz",
"integrity": "sha512-Gp+53zIEb68qTuyagcalDMVn1s0WrxiGBQJbEjShOdv3CYmbPIJEAN0Qtn4rCa7XgODoEa7HHuz8GoYgIpIzog==",
"requires": {
"asn1.js": "^5.2.0"
}
},
"json5": { "json5": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
@ -1940,6 +1979,11 @@
"mime-db": "1.40.0" "mime-db": "1.40.0"
} }
}, },
"minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
},
"minimatch": { "minimatch": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
@ -2984,8 +3028,7 @@
"typescript": { "typescript": {
"version": "3.6.3", "version": "3.6.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz",
"integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==", "integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw=="
"dev": true
}, },
"union-value": { "union-value": {
"version": "1.0.1", "version": "1.0.1",

View File

@ -17,9 +17,11 @@
"bcrypt": "^3.0.6", "bcrypt": "^3.0.6",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"config": "^3.2.2", "config": "^3.2.2",
"cookie-parser": "^1.4.4",
"dirty": "^1.1.0", "dirty": "^1.1.0",
"express": "^4.17.1", "express": "^4.17.1",
"flags": "^0.1.3", "flags": "^0.1.3",
"jose": "^1.15.1",
"mysql": "^2.17.1", "mysql": "^2.17.1",
"node-media-server": ">=2.1.3 <3.0.0", "node-media-server": ">=2.1.3 <3.0.0",
"nunjucks": "^3.2.0", "nunjucks": "^3.2.0",

View File

@ -1,4 +1,5 @@
import * as db from "./database" import * as db from "./database"
import { unregisterUser } from "./irc";
var config: any; var config: any;
function init(conf: object){ function init(conf: object){
@ -20,18 +21,26 @@ async function register(name: string, password: string, confirm: string) {
return {"error":""}; return {"error":""};
} }
async function update(name: string, password: string, title: string, bio: string, record: boolean){ async function update(fields: object){
if(!name || !password) return {"error":"Insufficient parameters"}; if(!fields['title'] && !fields['bio'] && (fields['rec'] !== 'true' && fields['rec'] !== 'false')) return {"error":"no valid fields specified"};
let auth: boolean = await db.validatePassword(name, password); let qs: string = "";
if(!auth) return {"error":"Username or Password Incorrect"}; let f: boolean = false;
await db.query('UPDATE user_meta set title='+db.raw.escape(title)+', about='+db.raw.escape(bio)+' where username='+db.raw.escape(name)); if(fields['title']) {qs += ' user_meta.title='+db.raw.escape(fields['title']);f = true;}
if(!record) await db.query('UPDATE users set record_flag=false where username='+db.raw.escape(name)); if(fields['bio']) {
else await db.query('UPDATE users set record_flag=true where username='+db.raw.escape(name)); if(f) qs+=',';
qs += ' user_meta.about='+db.raw.escape(fields['bio']);
f=true;
}
if(typeof(fields['rec']) === 'boolean' || typeof(fields['rec']) === 'number') {
if(f) qs+=',';
qs += ' users.record_flag='+db.raw.escape(fields['rec']);
}
await db.query('UPDATE users,user_meta SET'+qs+' WHERE users.username='+db.raw.escape(fields['name'])+' AND user_meta.username='+db.raw.escape(fields['name']));
return {"success":""}; return {"success":""};
} }
async function changepwd(name: string, password: string, newpwd: string){ async function changepwd(name: string, password: string, newpwd: string){
if(!name || !password) return {"error":"Insufficient parameters"}; if(!name || !password || !newpwd) return {"error":"Insufficient parameters"};
let auth: boolean = await db.validatePassword(name, password); let auth: boolean = await db.validatePassword(name, password);
if(!auth) return {"error":"Username or Password Incorrect"}; if(!auth) return {"error":"Username or Password Incorrect"};
let newhash: string = await db.hash(newpwd); let newhash: string = await db.hash(newpwd);
@ -39,13 +48,17 @@ async function changepwd(name: string, password: string, newpwd: string){
return {"success":""}; return {"success":""};
} }
async function changesk(name: string, password: string){ async function changesk(name: string){
if(!name || !password) return {"error":"Insufficient parameters"};
let auth: boolean = await db.validatePassword(name, password);
if(!auth) return {"error":"Username or Password Incorrect"};
let key: string = await db.genKey(); let key: string = await db.genKey();
await db.query('UPDATE users set stream_key='+db.raw.escape(key)+'where username='+db.raw.escape(name)+' limit 1'); await db.query('UPDATE users set stream_key='+db.raw.escape(key)+'where username='+db.raw.escape(name)+' limit 1');
return {"success":key}; return {"success":key};
} }
export { init, register, update, changepwd, changesk }; async function login(name: string, password: string){
if(!name || !password) return {"error":"Insufficient parameters"};
let auth: boolean = await db.validatePassword(name, password);
if(!auth) return {"error":"Username or Password Incorrect"};
return false;
}
export { init, register, update, changepwd, changesk, login };

View File

@ -4,22 +4,35 @@ import * as bodyparser from "body-parser";
import * as fs from "fs"; import * as fs from "fs";
import * as socketio from "socket.io"; import * as socketio from "socket.io";
import * as http from "http"; import * as http from "http";
import * as cookies from "cookie-parser";
import * as dirty from "dirty"; import * as dirty from "dirty";
import * as api from "./api"; import * as api from "./api";
import * as db from "./database"; import * as db from "./database";
import * as irc from "./irc"; import * as irc from "./irc";
import { readFileSync, writeFileSync } from "fs";
import { JWT, JWK } from "jose";
import { strict } from "assert";
import { parse } from "path";
const app = express(); const app = express();
const server = http.createServer(app); const server = http.createServer(app);
const io = socketio(server); const io = socketio(server);
const store = dirty(); const store = dirty();
var jwkey;
try{
jwkey = JWK.asKey(readFileSync('./config/jwt.pem'));
} catch (e) {
console.log("No key found for JWT signing, generating one now.");
jwkey = JWK.generateSync('RSA', 2048, { use: 'sig' });
writeFileSync('./config/jwt.pem', jwkey.toPEM(true));
}
var njkconf; var njkconf;
async function init(satyr: any, port: number, ircconf: any){ async function init(satyr: any, port: number, ircconf: any){
njk.configure('templates', { njk.configure('templates', {
autoescape: true, autoescape : true,
express : app, express : app,
watch: false watch : true
}); });
njkconf ={ njkconf ={
sitename: satyr.name, sitename: satyr.name,
@ -28,88 +41,287 @@ async function init(satyr: any, port: number, ircconf: any){
rootredirect: satyr.rootredirect, rootredirect: satyr.rootredirect,
version: satyr.version version: satyr.version
}; };
app.use(cookies());
app.use(bodyparser.json()); app.use(bodyparser.json());
app.use(bodyparser.urlencoded({ extended: true })); app.use(bodyparser.urlencoded({ extended: true }));
//site handlers //site handlers
await initSite(satyr.registration);
//api handlers
await initAPI();
//static files if nothing else matches first
app.use(express.static(satyr.directory));
//404 Handler
app.use(function (req, res, next) {
if(tryDecode(req.cookies.Authorization)) {
res.status(404).render('404.njk', Object.assign({auth: {is: true, name: JWT.decode(req.cookies.Authorization)['username']}}, njkconf));
}
else res.status(404).render('404.njk', njkconf);
//res.status(404).render('404.njk', njkconf);
});
await initChat(ircconf);
server.listen(port);
}
async function newNick(socket, skip?: boolean) {
if(socket.handshake.headers['cookie'] && !skip){
let c = await parseCookie(socket.handshake.headers['cookie']);
let t = await validToken(c['Authorization']);
if(t) return t['username'];
}
//i just realized how shitty of an idea this is
let n: string = 'Guest'+Math.floor(Math.random() * Math.floor(1000));
if(store.get(n)) return newNick(socket, true);
else {
store.set(n, socket.id);
return n;
}
}
async function chgNick(socket, nick) {
let rooms = Object.keys(socket.rooms);
for(let i=1;i<rooms.length;i++){
io.to(rooms[i]).emit('ALERT', socket.nick+' is now known as '+nick);
}
store.rm(socket.nick);
store.set(nick, socket.id);
socket.nick = nick;
}
async function genToken(u: string){
return await JWT.sign({
username: u
}, jwkey, {
expiresIn: '1 week',
iat: true,
kid: false
});//set jwt
}
async function validToken(t: any){
try {
let token = JWT.verify(t, jwkey);
return token;
}
catch (err){
return false;
}
}
function tryDecode(t: any){
try {
return JWT.decode(t);
}
catch (err){
return false;
}
}
async function parseCookie(c){
if(typeof(c) !== 'string' || !c.includes('=')) return {};
return Object.assign({[c.split('=')[0].trim()]:c.split('=')[1].split(';')[0].trim()}, await parseCookie(c.split(/;(.+)/)[1]));
}
async function initAPI() {
app.get('/api/users/live', (req, res) => {
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.post('/api/register', (req, res) => {
api.register(req.body.username, req.body.password, req.body.confirm).then( (result) => {
if(result[0]) return genToken(req.body.username).then((t) => {
res.cookie('Authorization', t);
res.send(result);
return;
});
res.send(result);
});
});
app.post('/api/user/update', (req, res) => {
validToken(req.cookies.Authorization).then((t) => {
if(t) {
return api.update({name: t['username'],
title: "title" in req.body ? req.body.title : false,
bio: "bio" in req.body ? req.body.bio : false,
rec: "record" in req.body ? req.body.record : "NA"
}).then((r) => {
res.send(r);
return;
});
}
else {
res.send('{"error":"invalid token"}');
return;
}
});
/*api.update(req.body.username, req.body.password, req.body.title, req.body.bio, req.body.record).then((result) => {
res.send(result);
});*/
});
app.post('/api/user/password', (req, res) => {
validToken(req.cookies.Authorization).then((t) => {
if(t) {
return api.changepwd(t['username'], req.body.password, req.body.newpassword).then((r) => {
res.send(r);
return;
});
}
else {
res.send('{"error":"invalid token"}');
return;
}
});
});
app.post('/api/user/streamkey', (req, res) => {
validToken(req.cookies.Authorization).then((t) => {
if(t) {
api.changesk(t['username']).then((r) => {
res.send(r);
});
}
else {
res.send('{"error":"invalid token"}');
}
});
});
app.post('/api/login', (req, res) => {
if(req.cookies.Authorization) validToken(req.cookies.Authorization).then((t) => {
if(t) {
if(t['exp'] - 86400 < Math.floor(Date.now() / 1000)){
return genToken(t['username']).then((t) => {
res.cookie('Authorization', t);
res.send('{"success":""}');
return;
});
}
else {
res.send('{"success":"already verified"}');
return;
}
}
else {
res.send('{"error":"invalid token"}');
return;
}
});
else {
api.login(req.body.username, req.body.password).then((result) => {
if(!result){
genToken(req.body.username).then((t) => {
res.cookie('Authorization', t);
res.send('{"success":""}');
})
}
else {
res.send(result);
}
});
}
})
}
async function initSite(openReg) {
app.get('/', (req, res) => { app.get('/', (req, res) => {
res.redirect(njkconf.rootredirect); res.redirect(njkconf.rootredirect);
}); });
app.get('/about', (req, res) => { app.get('/about', (req, res) => {
res.render('about.njk', njkconf); if(tryDecode(req.cookies.Authorization)) {
res.render('about.njk', Object.assign({auth: {is: true, name: JWT.decode(req.cookies.Authorization)['username']}}, njkconf));
}
else res.render('about.njk',njkconf);
}); });
app.get('/users', (req, res) => { app.get('/users', (req, res) => {
db.query('select username from users').then((result) => { db.query('select username from users').then((result) => {
res.render('list.njk', Object.assign({list: result}, njkconf)); if(tryDecode(req.cookies.Authorization)) {
res.render('list.njk', Object.assign({list: result}, {auth: {is: true, name: JWT.decode(req.cookies.Authorization)['username']}}, njkconf));
}
else res.render('list.njk', Object.assign({list: result}, njkconf));
//res.render('list.njk', Object.assign({list: result}, njkconf));
}); });
}); });
app.get('/users/live', (req, res) => { app.get('/users/live', (req, res) => {
db.query('select username,title from user_meta where live=1;').then((result) => { db.query('select username,title from user_meta where live=1;').then((result) => {
res.render('live.njk', Object.assign({list: result}, njkconf)); if(tryDecode(req.cookies.Authorization)) {
}); res.render('live.njk', Object.assign({list: result}, {auth: {is: true, name: JWT.decode(req.cookies.Authorization)['username']}}, njkconf));
});
app.get('/users/*', (req, res) => {
db.query('select username,title,about from user_meta where username='+db.raw.escape(req.url.split('/')[2].toLowerCase())).then((result) => {
if(result[0]){
res.render('user.njk', Object.assign(result[0], njkconf));
} }
else res.render('404.njk', njkconf); else res.render('live.njk', Object.assign({list: result}, njkconf));
//res.render('live.njk', Object.assign({list: result}, njkconf));
}); });
}); });
app.get('/vods/*', (req, res) => { app.get('/users/:user', (req, res) => {
db.query('select username from user_meta where username='+db.raw.escape(req.url.split('/')[2].toLowerCase())).then((result) => { db.query('select username,title,about from user_meta where username='+db.raw.escape(req.params.user)).then((result) => {
if(result[0]){ if(result[0]){
fs.readdir('./site/live/'+njkconf.user, {withFileTypes: true} , (err, files) => { if(tryDecode(req.cookies.Authorization)) {
res.render('vods.njk', Object.assign({user: result[0].username, list: files.filter(fn => fn.name.endsWith('.mp4'))}, njkconf)); res.render('user.njk', Object.assign(result[0], {auth: {is: true, name: JWT.decode(req.cookies.Authorization)['username']}}, njkconf));
}
else res.render('user.njk', Object.assign(result[0], njkconf));
//res.render('user.njk', Object.assign(result[0], njkconf));
}
else if(tryDecode(req.cookies.Authorization)) {
res.status(404).render('404.njk', Object.assign({auth: {is: true, name: JWT.decode(req.cookies.Authorization)['username']}}, njkconf));
}
else res.status(404).render('404.njk', njkconf);
});
});
app.get('/vods/:user', (req, res) => {
db.query('select username from user_meta where username='+db.raw.escape(req.params.user)).then((result) => {
if(result[0]){
fs.readdir('./site/live/'+result[0].username, {withFileTypes: true} , (err, files) => {
if(tryDecode(req.cookies.Authorization)) {
res.render('vods.njk', Object.assign({user: result[0].username, list: files.filter(fn => fn.name.endsWith('.mp4'))}, {auth: {is: true, name: JWT.decode(req.cookies.Authorization)['username']}}, njkconf));
}
else res.render('vods.njk', Object.assign({user: result[0].username, list: files.filter(fn => fn.name.endsWith('.mp4'))}, njkconf));
//res.render('vods.njk', Object.assign({user: result[0].username, list: files.filter(fn => fn.name.endsWith('.mp4'))}, njkconf));
}); });
} }
else res.render('404.njk', njkconf); else if(tryDecode(req.cookies.Authorization)) {
res.status(404).render('404.njk', Object.assign({auth: {is: true, name: JWT.decode(req.cookies.Authorization)['username']}}, njkconf));
}
else res.status(404).render('404.njk', njkconf);
}); });
}); });
app.get('/login', (req, res) => {
if(tryDecode(req.cookies.Authorization)) {
res.redirect(njkconf.rootredirect);
}
else res.render('login.njk',njkconf);
});
app.get('/register', (req, res) => { app.get('/register', (req, res) => {
res.render('registration.njk', njkconf); if(tryDecode(req.cookies.Authorization) || !openReg) {
res.redirect(njkconf.rootredirect);
}
else res.render('registration.njk',njkconf);
}); });
app.get('/profile', (req, res) => { app.get('/profile', (req, res) => {
res.render('profile.njk', njkconf); if(tryDecode(req.cookies.Authorization)) {
res.render('profile.njk', Object.assign({auth: {is: true, name: JWT.decode(req.cookies.Authorization)['username']}}, njkconf));
}
else res.redirect(njkconf.rootredirect);
}); });
app.get('/changepwd', (req, res) => { app.get('/changepwd', (req, res) => {
res.render('changepwd.njk', njkconf); if(tryDecode(req.cookies.Authorization)) {
}); res.render('changepwd.njk', Object.assign({auth: {is: true, name: JWT.decode(req.cookies.Authorization)['username']}}, njkconf));
app.get('/changesk', (req, res) => { }
res.render('changesk.njk', njkconf); else res.redirect(njkconf.rootredirect);
}); });
app.get('/chat', (req, res) => { app.get('/chat', (req, res) => {
res.render('chat.html', njkconf); res.render('chat.html', njkconf);
}); });
app.get('/help', (req, res) => { app.get('/help', (req, res) => {
res.render('help.njk', njkconf); if(tryDecode(req.cookies.Authorization)) {
}); res.render('help.njk', Object.assign({auth: {is: true, name: JWT.decode(req.cookies.Authorization)['username']}}, njkconf));
//api handlers }
app.post('/api/register', (req, res) => { else res.render('help.njk',njkconf);
api.register(req.body.username, req.body.password, req.body.confirm).then( (result) => {
res.send(result);
});
});
app.post('/api/user', (req, res) => {
api.update(req.body.username, req.body.password, req.body.title, req.body.bio, req.body.record).then((result) => {
res.send(result);
});
});
app.post('/api/user/password', (req, res) => {
api.changepwd(req.body.username, req.body.password, req.body.newpassword).then((result) => {
res.send(result);
});
});
app.post('/api/user/streamkey', (req, res) => {
api.changesk(req.body.username, req.body.password).then((result) => {
res.send(result);
})
});
//static files if nothing else matches first
app.use(express.static('site'));
//404 Handler
app.use(function (req, res, next) {
res.status(404).render('404.njk', njkconf);
}); });
}
async function initChat(ircconf: any) {
//irc peering //irc peering
if(ircconf.enable){ if(ircconf.enable){
await irc.connect({ await irc.connect({
@ -190,26 +402,6 @@ async function init(satyr: any, port: number, ircconf: any){
else socket.emit('ALERT', 'Not authorized to do that.'); else socket.emit('ALERT', 'Not authorized to do that.');
}); });
}); });
server.listen(port);
}
async function newNick(socket) {
//i just realized how shitty of an idea this is
let n: string = 'Guest'+Math.floor(Math.random() * Math.floor(1000));
if(store.get(n)) return newNick(socket);
else {
store.set(n, socket.id);
return n;
}
}
async function chgNick(socket, nick) {
let rooms = Object.keys(socket.rooms);
for(let i=1;i<rooms.length;i++){
io.to(rooms[i]).emit('ALERT', socket.nick+' is now known as '+nick);
}
store.rm(socket.nick);
store.set(nick, socket.id);
socket.nick = nick;
} }
export { init }; export { init };

View File

@ -77,7 +77,7 @@ function init (mediaconfig: any, satyrconfig: any) {
if(app === satyrconfig.privateEndpoint) { if(app === satyrconfig.privateEndpoint) {
db.query('update user_meta,users set user_meta.live=false where users.stream_key='+db.raw.escape(key)); db.query('update user_meta,users set user_meta.live=false where users.stream_key='+db.raw.escape(key));
db.query('select username from users where stream_key='+db.raw.escape(key)+' limit 1').then(async (results) => { db.query('select username from users where stream_key='+db.raw.escape(key)+' limit 1').then(async (results) => {
keystore.rm(results[0].username); if(results[0]) keystore.rm(results[0].username);
}); });
} }

View File

@ -8,7 +8,7 @@
<body> <body>
<div id="wrapper"> <div id="wrapper">
<div id="header"> <div id="header">
<span style="float:left;"><h4><a href="/">{{ sitename }}</a> | <a href="/users">Users</a> <a href="/users/live">Live</a> <a href="/about">About</a></h4></span><span style="float:right;"><h4><a href="/help">Help</a> | <a href="/profile">Profile</a></h4></span> <span style="float:left;"><h4><a href="/">{{ sitename }}</a> | <a href="/users">Users</a> <a href="/users/live">Live</a> <a href="/about">About</a></h4></span><span style="float:right;"><h4><a href="/help">Help</a> | {% if auth.is %}<a href="/profile">{{ auth.name }}{% else %}<a href="/login">Log In{% endif %}</a></h4></span>
</div> </div>
<div id="content"> <div id="content">
{% block content %} {% block content %}

View File

@ -1,11 +1,10 @@
{% extends "base.njk" %} {% extends "base.njk" %}
{% block content %} {% block content %}
<h3>Change your password on {{ sitename }}</h3><span style="font-size: small;">Not registered yet? Sign up <a href="/register">here</a>.</br> Update your <a href="/profile">profile</a> or <a href="/changesk">stream key</a>.</span> <h3>Change your password on {{ sitename }}</h3>
<p></p> <p></p>
<form action="/api/user/password" method="POST" target="responseFrame"> <form action="/api/user/password" method="POST" target="responseFrame">
Username: </br><input type="text" name="username" style="min-width: 300px" placeholder="e.g. lain"/></br> Old Password: </br><input type="password" name="password" style="min-width: 300px"/></br>
Password: </br><input type="password" name="password" style="min-width: 300px"/></br> New Password: </br><input type="password" name="newpassword" style="min-width: 300px"/></br></br>
New Password: </br><input type="password" name="newpassword" style="min-width: 300px"/></br>
<input type="submit" value="Submit"> <input type="submit" value="Submit">
</form> </form>
<iframe name="responseFrame" border="0" frameborder="0" style="display: inline;"></iframe> <iframe name="responseFrame" border="0" frameborder="0" style="display: inline;"></iframe>

View File

@ -1,11 +0,0 @@
{% extends "base.njk" %}
{% block content %}
<h3>Get a new stream key on {{ sitename }}</h3><span style="font-size: small;">Not registered yet? Sign up <a href="/register">here</a>.</br> Update your <a href="/profile">profile</a> or <a href="/changepwd">password</a>.</span>
<p></p>
<form action="/api/user/streamkey" method="POST" target="responseFrame">
Username: </br><input type="text" name="username" style="min-width: 300px" placeholder="e.g. lain"/></br>
Password: </br><input type="password" name="password" style="min-width: 300px"/></br>
<input type="submit" value="Submit">
</form>
<iframe name="responseFrame" border="0" frameborder="0" style="display: inline;"></iframe>
{% endblock %}

10
templates/login.njk Normal file
View File

@ -0,0 +1,10 @@
{% extends "base.njk" %}
{% block content %}
<h3>Log in to {{ sitename }}</h3><span style="font-size: small;">Not registered yet? Sign up <a href="/register">here</a>.</br></br></span>
<form action="/api/login" method="POST" target="responseFrame">
Username: </br><input type="text" name="username" style="min-width: 300px" placeholder="e.g. lain"/></br>
Password: </br><input type="password" name="password" style="min-width: 300px"/></br>
<input type="submit" value="Submit">
</form>
<iframe name="responseFrame" border="0" frameborder="0" style="display: inline;"></iframe>
{% endblock %}

View File

@ -1,14 +1,15 @@
{% extends "base.njk" %} {% extends "base.njk" %}
{% block content %} {% block content %}
<h3>Update your profile on {{ sitename }}</h3><span style="font-size: small;">Not registered yet? Sign up <a href="/register">here</a>.</br> Change your <a href="/changepwd">password</a> or <a href="/changesk">stream key</a>.</span> <h3>Update your profile on {{ sitename }}</h3><span style="font-size: small;">Or, change your <a href="/changepwd">password</a>.</span>
<p></p> <p></p>
<form action="/api/user" method="POST" target="responseFrame"> <form action="/api/user/update" method="POST" target="responseFrame">
Username: </br><input type="text" name="username" style="min-width: 300px" placeholder="e.g. lain"/></br>
Password: </br><input type="password" name="password" style="min-width: 300px"/></br>
Stream Title: </br><input type="text" name="title" style="min-width: 300px"/></br> Stream Title: </br><input type="text" name="title" style="min-width: 300px"/></br>
Bio: </br><input type="text" name="bio" style="min-width: 300px; min-height: 150px;"/></br> Bio: </br><input type="text" name="bio" style="min-width: 300px; min-height: 150px;"/></br>
Record VODs: <input type="checkbox" name="record" value="true"></br> Record VODs: <input type="radio" name="record" value="true"> Yes<input type="radio" name="record" value="false" /> No</br></br>
<input type="submit" value="Submit"> <input type="submit" value="Update Profile">
</form></br>
<form action="/api/user/streamkey" method="POST" target="responseFrame">
<input type="submit" value="Request New Stream Key">
</form> </form>
<iframe name="responseFrame" border="0" frameborder="0" style="display: inline;"></iframe> <iframe name="responseFrame" border="0" frameborder="0" style="display: inline;"></iframe>
{% endblock %} {% endblock %}

View File

@ -1,17 +1,19 @@
{% extends "base.njk" %} {% extends "base.njk" %}
{% block content %} {% block content %}
<div id="jscontainer" style="height: 100%;"> <h3>Register on {{ sitename }}</h3><span style="font-size: small;">Already registered? Log in <a href="/login">here</a>.</br></br></span>
<div id="jschild" style="width: 50%;height: 100%;text-align: left;margin: 20px;"> <!--<div id="jscontainer" style="height: 100%;">
<div id="jschild" style="width: 50%;height: 100%;text-align: left;margin: 20px;">-->
<form action="/api/register" method="POST" target="responseFrame"> <form action="/api/register" method="POST" target="responseFrame">
Username: </br><input type="text" name="username" style="min-width: 300px" placeholder="e.g. lain"/></br> Username: </br><input type="text" name="username" style="min-width: 300px" placeholder="e.g. lain"/></br>
Password: </br><input type="password" name="password" style="min-width: 300px"/></br> Password: </br><input type="password" name="password" style="min-width: 300px"/></br>
Confirm: </br><input type="password" name="confirm" style="min-width: 300px"/></br> Confirm: </br><input type="password" name="confirm" style="min-width: 300px"/></br></br>
<input type="submit" value="Submit"> <input type="submit" value="Submit">
</form> </form></br>
<!--</div>
<div id="jschild" style="width: 50%;height: 100%;text-align: left;margin: 20px;">-->
{% include "tos.html" %}</br>
<iframe name="responseFrame" border="0" frameborder="0" style="display: inline;"></iframe> <iframe name="responseFrame" border="0" frameborder="0" style="display: inline;"></iframe>
</div> <!--</div>
<div id="jschild" style="width: 50%;height: 100%;text-align: left;margin: 20px;"> </div>-->
{% include "tos.html" %}
</div>
</div>
{% endblock %} {% endblock %}