From 8caad60a43399c7b1ff37bd25d3d98c37fcbf898 Mon Sep 17 00:00:00 2001 From: knotteye Date: Tue, 13 Oct 2020 15:29:47 -0500 Subject: [PATCH 1/6] Add functions for generating and using invite codes --- src/api.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/api.ts b/src/api.ts index a803d63..abf2e94 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,4 +1,5 @@ import * as db from "./database"; +import * as base64id from "base64id"; import { config } from "./config"; import {unlink} from "fs"; @@ -97,4 +98,18 @@ async function getConfig(username: string, all?: boolean): Promise{ return t; } -export { register, update, changepwd, changesk, login, updateChat, deleteVODs, getConfig }; \ No newline at end of file +async function genInvite(user: string): Promise{ + var invitecode: string = base64id.generateId(); + await db.query('INSERT INTO invites (code) VALUES ('+invitecode+')'); + return invitecode; +} + +async function useInvite(code: string): Promise{ + if(typeof(code) !== "string" || code === "") return false; + var result = await db.query('SELECT code FROM invites WHERE code='+db.raw.escape(code)); + if(!result[0] || result[0]['code'] !== code) return false; + await db.query('DELETE FROM invites WHERE code='+db.raw.escape(code)); + return true; +} + +export { register, update, changepwd, changesk, login, updateChat, deleteVODs, getConfig, genInvite, useInvite }; \ No newline at end of file From 9605ff8c92a3abd523b1959d352e456d6266aad5 Mon Sep 17 00:00:00 2001 From: knotteye Date: Tue, 13 Oct 2020 15:48:39 -0500 Subject: [PATCH 2/6] Add a way to generate invites from the command line. Add database migration script. --- package.json | 5 +++-- src/api.ts | 4 ++-- src/cli.ts | 13 ++++++++++++- src/db/2.ts | 8 ++++++++ 4 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 src/db/2.ts diff --git a/package.json b/package.json index 6c3531f..8d685ac 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,10 @@ "author": "knotteye", "scripts": { "start": "ts-node src/index.ts", - "user": "ts-node src/cli.ts", + "cli": "ts-node src/cli.ts", "setup": "sh install/setup.sh", - "migrate": "ts-node src/migrate.ts" + "migrate": "ts-node src/migrate.ts", + "invite": "ts-node src/cli.ts --invite" }, "repository": { "type": "git", diff --git a/src/api.ts b/src/api.ts index abf2e94..55d6437 100644 --- a/src/api.ts +++ b/src/api.ts @@ -98,9 +98,9 @@ async function getConfig(username: string, all?: boolean): Promise{ return t; } -async function genInvite(user: string): Promise{ +async function genInvite(): Promise{ var invitecode: string = base64id.generateId(); - await db.query('INSERT INTO invites (code) VALUES ('+invitecode+')'); + await db.query('INSERT INTO invites (code) VALUES (\"'+invitecode+'\")'); return invitecode; } diff --git a/src/cli.ts b/src/cli.ts index 110ecc6..1109154 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,4 +1,5 @@ -import * as db from "./database" +import * as db from "./database"; +import * as api from "./api"; import * as flags from "flags"; db.init(); @@ -6,6 +7,7 @@ db.init(); flags.defineString('adduser', '', 'User to add'); flags.defineString('rmuser', '', 'User to remove'); flags.defineString('password', '', 'password to hash'); +flags.defineBoolean('invite', false, 'generate invite code'); flags.parse(); @@ -23,4 +25,13 @@ if(flags.get('rmuser') !== ''){ else console.log("Could not remove user."); process.exit(); }); +} + +if(flags.get('invite')){ + var config = require("./config").config; + api.genInvite().then((r: string) => { + console.log('invite code: '+r); + console.log('Direct the user to https://'+config['satyr']['domain']+'/invite/'+r); + process.exit(); + }); } \ No newline at end of file diff --git a/src/db/2.ts b/src/db/2.ts new file mode 100644 index 0000000..cfe7ae0 --- /dev/null +++ b/src/db/2.ts @@ -0,0 +1,8 @@ +import * as db from "../database"; + +async function run () { + await db.query('CREATE TABLE IF NOT EXISTS invites(code VARCHAR(150))'); + await db.query('INSERT INTO db_meta (version) VALUES (2)'); +} + +export { run } \ No newline at end of file From 67de11e66bab5609ec60c809dca8faeefab7319c Mon Sep 17 00:00:00 2001 From: knotteye Date: Tue, 13 Oct 2020 16:12:07 -0500 Subject: [PATCH 3/6] Add API handling of invite codes, add web page for inviting users. --- src/api.ts | 13 ++++++++----- src/config.ts | 2 +- src/http.ts | 27 +++++++++++++++++++++++++++ templates/invite.njk | 20 ++++++++++++++++++++ 4 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 templates/invite.njk diff --git a/src/api.ts b/src/api.ts index 55d6437..d421bab 100644 --- a/src/api.ts +++ b/src/api.ts @@ -3,8 +3,8 @@ import * as base64id from "base64id"; import { config } from "./config"; import {unlink} from "fs"; -async function register(name: string, password: string, confirm: string): Promise { - if(!config['satyr']['registration']) return {"error":"registration disabled"}; +async function register(name: string, password: string, confirm: string, invite?: boolean): Promise { + if(!config['satyr']['registration'] && !invite) return {"error":"registration disabled"}; if(name.includes(';') || name.includes(' ') || name.includes('\'')) return {"error":"illegal characters"}; if(password !== confirm) return {"error":"mismatched passwords"}; for(let i=0;i{ return invitecode; } -async function useInvite(code: string): Promise{ +async function validInvite(code: string): Promise{ if(typeof(code) !== "string" || code === "") return false; var result = await db.query('SELECT code FROM invites WHERE code='+db.raw.escape(code)); if(!result[0] || result[0]['code'] !== code) return false; - await db.query('DELETE FROM invites WHERE code='+db.raw.escape(code)); return true; } -export { register, update, changepwd, changesk, login, updateChat, deleteVODs, getConfig, genInvite, useInvite }; \ No newline at end of file +async function useInvite(code: string): Promise{ + if(validInvite(code)) await db.query('DELETE FROM invites WHERE code='+db.raw.escape(code)); +} + +export { register, update, changepwd, changesk, login, updateChat, deleteVODs, getConfig, genInvite, useInvite, validInvite }; \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index 4bd6ec0..0fdeaa4 100644 --- a/src/config.ts +++ b/src/config.ts @@ -16,7 +16,7 @@ const config: Object = { domain: '', registration: false, email: null, - restrictedNames: [ 'live', 'user', 'users', 'register', 'login' ], + restrictedNames: [ 'live', 'user', 'users', 'register', 'login', 'invite' ], rootredirect: '/users/live', version: process.env.npm_package_version, }, localconfig['satyr']), diff --git a/src/http.ts b/src/http.ts index b5f9c53..4f2544d 100644 --- a/src/http.ts +++ b/src/http.ts @@ -224,6 +224,21 @@ async function initAPI() { }); }); app.post('/api/register', (req, res) => { + if("invite" in req.body){ + if(api.validInvite(req.body.invite)){ + api.register(req.body.username, req.body.password, req.body.confirm, true).then((result) => { + if(result[0]) return genToken(req.body.username).then((t) => { + res.cookie('Authorization', t, {maxAge: 604800000, httpOnly: true, sameSite: 'Lax'}); + res.json(result); + api.useInvite(req.body.invite); + return; + }); + res.json(result); + }); + } + else res.json({error: "invalid invite code"}); + } + else 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, {maxAge: 604800000, httpOnly: true, sameSite: 'Lax'}); @@ -486,6 +501,18 @@ async function initSite(openReg) { } else res.render('login.njk',njkconf); }); + app.get('/invite/:code', (req, res) => { + if(tryDecode(req.cookies.Authorization)) { + res.redirect('/profile'); + } + else res.render('invite.njk',Object.assign({icode: req.params.code}, njkconf)); + }); + app.get('/invite', (req, res) => { + if(tryDecode(req.cookies.Authorization)) { + res.redirect('/profile'); + } + else res.render('invite.njk',Object.assign({icode: ""}, njkconf)); + }); app.get('/register', (req, res) => { if(tryDecode(req.cookies.Authorization) || !openReg) { res.redirect(njkconf.rootredirect); diff --git a/templates/invite.njk b/templates/invite.njk new file mode 100644 index 0000000..9b2b30b --- /dev/null +++ b/templates/invite.njk @@ -0,0 +1,20 @@ +{% extends "base.njk" %} +{% block content %} +

You've been invited to {{ sitename }}

Already registered? Log in here.

+ +
+ Username:

+ Password:

+ Confirm:

+ Invite Code:


+ +

+ + + {% include "tos.html" %}
+ + +{% endblock %} \ No newline at end of file From eba53c37325c4bbac1044d74a49159287a251ff3 Mon Sep 17 00:00:00 2001 From: knotteye Date: Tue, 13 Oct 2020 16:16:37 -0500 Subject: [PATCH 4/6] Rework invitation UI a bit, document API changes --- docs/REST.md | 4 +++- src/http.ts | 6 ------ templates/invite.njk | 4 ++-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/docs/REST.md b/docs/REST.md index 3160cf7..c6004a0 100644 --- a/docs/REST.md +++ b/docs/REST.md @@ -91,7 +91,9 @@ Register a new user. **Authentication**: no -**Parameters**: Username, password, confirm +**Parameters**: Username, password, confirm, invite(optional) + +Invite is an optional invite code to bypass disabled registration. **Response**: If successful, returns a json object with the users stream key. Otherwise returns `{error: "error reason"}` diff --git a/src/http.ts b/src/http.ts index 4f2544d..0c0aa6b 100644 --- a/src/http.ts +++ b/src/http.ts @@ -507,12 +507,6 @@ async function initSite(openReg) { } else res.render('invite.njk',Object.assign({icode: req.params.code}, njkconf)); }); - app.get('/invite', (req, res) => { - if(tryDecode(req.cookies.Authorization)) { - res.redirect('/profile'); - } - else res.render('invite.njk',Object.assign({icode: ""}, njkconf)); - }); app.get('/register', (req, res) => { if(tryDecode(req.cookies.Authorization) || !openReg) { res.redirect(njkconf.rootredirect); diff --git a/templates/invite.njk b/templates/invite.njk index 9b2b30b..c181635 100644 --- a/templates/invite.njk +++ b/templates/invite.njk @@ -6,8 +6,8 @@
Username:

Password:

- Confirm:

- Invite Code:


+ Confirm:


+

From acce235812e0181616321d5fdf58032e2ed57ede Mon Sep 17 00:00:00 2001 From: knotteye Date: Tue, 13 Oct 2020 16:17:15 -0500 Subject: [PATCH 5/6] Increment minor version due to backwards compatible API changes --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8d685ac..e2b210f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "satyr", - "version": "0.9.3", + "version": "0.9.4", "description": "A livestreaming server.", "license": "AGPL-3.0", "author": "knotteye", From 1a410a597a499f1ed9d589930e489910f8525329 Mon Sep 17 00:00:00 2001 From: knotteye Date: Tue, 13 Oct 2020 16:29:13 -0500 Subject: [PATCH 6/6] Fix a bug checking the validity of invite codes --- src/http.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/http.ts b/src/http.ts index 0c0aa6b..8eb06dc 100644 --- a/src/http.ts +++ b/src/http.ts @@ -225,18 +225,20 @@ async function initAPI() { }); app.post('/api/register', (req, res) => { if("invite" in req.body){ - if(api.validInvite(req.body.invite)){ - api.register(req.body.username, req.body.password, req.body.confirm, true).then((result) => { - if(result[0]) return genToken(req.body.username).then((t) => { - res.cookie('Authorization', t, {maxAge: 604800000, httpOnly: true, sameSite: 'Lax'}); + api.validInvite(req.body.invite).then((v) => { + if(v){ + api.register(req.body.username, req.body.password, req.body.confirm, true).then((result) => { + if(result[0]) return genToken(req.body.username).then((t) => { + res.cookie('Authorization', t, {maxAge: 604800000, httpOnly: true, sameSite: 'Lax'}); + res.json(result); + api.useInvite(req.body.invite); + return; + }); res.json(result); - api.useInvite(req.body.invite); - return; }); - res.json(result); - }); - } - else res.json({error: "invalid invite code"}); + } + else res.json({error: "invalid invite code"}); + }); } else api.register(req.body.username, req.body.password, req.body.confirm).then( (result) => {