From e23f52d2fa2f4f092aa4a8a0aeaf5f83a676a78f Mon Sep 17 00:00:00 2001 From: RemainingDev <161089154+RemainingDev@users.noreply.github.com> Date: Wed, 12 Feb 2025 20:48:59 +0100 Subject: [PATCH 1/2] Add Registration via invite links --- config/texts.example.ts | 2 +- src/classes/extension.ts | 7 +- src/extensions/invite/index.html | 4 +- src/extensions/invite/index.ts | 130 +++++++++++++++++++++++++--- src/extensions/invite/login.html | 0 src/extensions/invite/register.html | 29 +++++++ src/extensions/invite/tables.ts | 9 +- src/extensions/root/index.ts | 22 ----- src/extensions/root/login.html | 3 +- 9 files changed, 161 insertions(+), 45 deletions(-) create mode 100644 src/extensions/invite/login.html create mode 100644 src/extensions/invite/register.html diff --git a/config/texts.example.ts b/config/texts.example.ts index dad6441..ea8c3dd 100644 --- a/config/texts.example.ts +++ b/config/texts.example.ts @@ -9,6 +9,6 @@ export default { Please do be aware of the fact that this is still very much in development and a lot of stuff will be improved with time.

- Register / Login + Login `, } diff --git a/src/classes/extension.ts b/src/classes/extension.ts index 7eefdfe..a1cbbff 100644 --- a/src/classes/extension.ts +++ b/src/classes/extension.ts @@ -153,13 +153,16 @@ export abstract class ExtensionBase implements Extension { key, value, { secure: true, - httpOnly: true + httpOnly: true, + path: '/' } ) else return cookie.serialize( key, - value + value, { + path: '/' + } ) } diff --git a/src/extensions/invite/index.html b/src/extensions/invite/index.html index 017474f..5f87456 100644 --- a/src/extensions/invite/index.html +++ b/src/extensions/invite/index.html @@ -10,7 +10,7 @@ ID - Link + Code Created at Used @@ -19,7 +19,7 @@ {% for link in invite_links %} {{link.id}} - {{link.link}} + {{link.code}} {{link.created_at}} {{"Yes" if link.used else "No"}} diff --git a/src/extensions/invite/index.ts b/src/extensions/invite/index.ts index ba576d3..1c3303a 100644 --- a/src/extensions/invite/index.ts +++ b/src/extensions/invite/index.ts @@ -1,3 +1,4 @@ +import crypto from 'crypto' import { ExtensionBase, Knex } from '../../modules.ts' import { unpack } from '../../util.ts' @@ -5,19 +6,35 @@ export default class extends ExtensionBase { override name = 'invite' override title = 'Invite' override tables = true - override admin_only = true - invite_URL = 'http://127.0.0.1:8000/invite/register/' + salt: string + + override init = (context: InitContext) => { + this.salt = context.modules.config.salt + return ExtensionBase.init(this, context) + } + + override requires_admin: Extension['requires_admin'] = (path) => { + if (['register', 'create_acc'].includes(path.at(0)??'')) { + return false + } + return true + } + override requires_login: Extension['requires_login'] = (path) => { + if (['register', 'create_acc'].includes(path.at(0)??'')) { + return false + } + return true + } override handle: Extension['handle'] = async (ctx) => { let [knex]: [Knex] = this.get_dependencies('Knex') - var location = ctx.path.shift() + let location = ctx.path.shift() switch (location) { case '': case undefined:{ - - var [invite_links, err] = await knex + let [invite_links, err] = await knex .query('_invite') .select('*') .then(unpack) @@ -25,28 +42,113 @@ export default class extends ExtensionBase { ctx.context.invite_links = invite_links return this.return_html(ctx, 'index') } - case 'create': + case 'create':{ const chars = 'abcdefghijklmnopqrstuvwxyz0123456789' - let random_chars = 'r' - for (let i = 0; i < 8; i++) { + let random_chars = '' + for (let i = 0; i < 10; i++) { random_chars += chars.charAt(Math.floor(Math.random() * chars.length)); } - let invite_link = this.invite_URL + random_chars + Date.now(); - const time = new Date().toLocaleTimeString('en-US', {hour12: false}); - + let code = random_chars await knex.query('_invite') // @ts-expect-error - .insert({link: invite_link, created_at: new Date().toLocaleTimeString('en-US', {hour12: false})}) - - ctx.context.invite_links = invite_links + .insert({code: code}) return this.return(ctx, undefined, location='/invite') + } + case 'register': { + const invite_code = ctx.args.get('code') + if (!invite_code) + return + + let [invite, err] = await knex + .query('_invite') + .select('used') + .where('code', invite_code) + .first() + .then(unpack) + + if (!err && !invite?.used){ + ctx.context.invite_code = invite_code + return this.return_html(ctx, 'register', undefined, 500, 200, { + "Set-Cookie": this.del_cookie('auth') + }) + } + return this.return(ctx, undefined, "/") + } + case 'create_acc':{ + if (ctx.data) + { + let form: {invite_code?: string, username?: string, password?: string} = ctx.data.form + if (form.username && form.password && form.invite_code) { + form.username = form.username.substring(0, 32) + + this.addUser(form.username, form.password, async (id?: number, err?: Error) => { + // if invalid credentials + if (err) { + ctx.context.auth_err = err + return this.return_html(ctx, 'login') + } + // success + else { + let auth = Buffer.from(form.username+":"+form.password).toString('base64') + await knex + .query('_invite') + // @ts-expect-error + .update({ + used: true, + user_id: id + }) + .where('code', form.invite_code) + .then() + return this.return_html(ctx, 'login', undefined, 500, 303, { + "Location": "/", + "Set-Cookie": this.set_cookie('auth', 'Basic '+auth, true) + }) + } + }) + } + } + return + } default: { return this.return_file(ctx, location) } } } + + private hash_pw(password: string): string { + return crypto.pbkdf2Sync(password, this.salt, 10000, 128, 'sha512').toString('base64') + } + + addUser(name: User['name'], password: User['password'], callback: (id?: number, err?: Error) => void) { + let [knex]: [Knex] = this.get_dependencies('Knex') + + password = this.hash_pw(password) + // Check if username is already taken + this.exists(name, (exists, err) => { + if (err) return callback(undefined, err) + if (exists) return callback(undefined, new Error("Username already taken")) + // add user to db + knex.query('user') + // @ts-expect-error + .insert({name, password, pfp_code: `seed=${name}`}) + .then((id) => callback(id[0] as unknown as number), (err) => callback(undefined, err)) + }) + } + + private exists(name: User['name'], callback: (exists: boolean, err?: Error) => void): void { + let [knex]: [Knex] = this.get_dependencies('Knex') + // check if name already exists + knex.query('user') + .select('id') + .where('name', name) + .then((value) => { + callback(!!value.length) + }, (err) => { + callback(false, err) + }) + } } diff --git a/src/extensions/invite/login.html b/src/extensions/invite/login.html new file mode 100644 index 0000000..e69de29 diff --git a/src/extensions/invite/register.html b/src/extensions/invite/register.html new file mode 100644 index 0000000..83c39f0 --- /dev/null +++ b/src/extensions/invite/register.html @@ -0,0 +1,29 @@ +{% extends "layout.html" %} + +{% block head %} + +{% endblock %} + + +{% block body %} +
+ + + + + + + + + + + + + + +
+ +
+ {{auth_err}} +
+{% endblock %} diff --git a/src/extensions/invite/tables.ts b/src/extensions/invite/tables.ts index ac25d49..cefcdee 100644 --- a/src/extensions/invite/tables.ts +++ b/src/extensions/invite/tables.ts @@ -14,10 +14,15 @@ export default class extends Tables { await knex.schema() .createTable('_invite', (table) => { table.increments('id').primary() - table.string('link').notNullable() - table.timestamp('created_at').notNullable().defaultTo(knex.raw('CURRENT_TIMESTAMP')) + table.string('code').notNullable() + table.datetime('created_at').notNullable().defaultTo(knex.raw('CURRENT_TIMESTAMP')) table.boolean('used').notNullable().defaultTo(false) + table.integer('user_id') + table.foreign('user_id', 'fk_user_id').references('_root_user.id') }) + await knex.query('_invite') + // @ts-expect-error + .insert({code: 'admin'}) }, }) diff --git a/src/extensions/root/index.ts b/src/extensions/root/index.ts index c72ece3..5b6354a 100644 --- a/src/extensions/root/index.ts +++ b/src/extensions/root/index.ts @@ -68,28 +68,6 @@ export default class extends ExtensionBase implements RootExtension { "Set-Cookie": this.set_cookie('auth', 'Basic '+auth, true) }) } - // Register - else if (form.register) { - if (form.username && form.password) { - form.username = form.username.substring(0, 32) - this.addUser(form.username, form.password, (err?: Error) => { - // if invalid credentials - if (err) { - ctx.context.auth_err = err - return this.return_html(ctx, 'login') - } - // success - else { - let auth = Buffer.from(form.username+":"+form.password).toString('base64') - return this.return_html(ctx, 'login', undefined, 500, 303, { - "Location": "/", - "Set-Cookie": this.set_cookie('auth', 'Basic '+auth, true) - }) - } - }) - } - return - } } // First load return this.return_html(ctx, 'login', undefined, 500, 200, { diff --git a/src/extensions/root/login.html b/src/extensions/root/login.html index a95b38e..94dd479 100644 --- a/src/extensions/root/login.html +++ b/src/extensions/root/login.html @@ -1,7 +1,7 @@ {% extends "layout.html" %} {% block body %} -

Register / Log in

+

Log in

@@ -16,7 +16,6 @@

Register / Log in

-
From 4fdfa5ed95540d66d4af21ae01f3ed9f2857f4c1 Mon Sep 17 00:00:00 2001 From: RemainingDev <161089154+RemainingDev@users.noreply.github.com> Date: Sat, 15 Feb 2025 20:14:47 +0100 Subject: [PATCH 2/2] Add Minecraft usernames --- src/extensions/invite/index.ts | 11 +++++++- src/extensions/invite/register.html | 5 ++++ src/extensions/minecraft/index.html | 9 +++++++ src/extensions/minecraft/index.ts | 41 ++++++++++++++++++++++++++++- src/extensions/minecraft/tables.ts | 26 ++++++++++++++++++ 5 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 src/extensions/minecraft/tables.ts diff --git a/src/extensions/invite/index.ts b/src/extensions/invite/index.ts index 1c3303a..ab4b9ca 100644 --- a/src/extensions/invite/index.ts +++ b/src/extensions/invite/index.ts @@ -1,6 +1,7 @@ import crypto from 'crypto' import { ExtensionBase, Knex } from '../../modules.ts' import { unpack } from '../../util.ts' +import minecraft from '../minecraft/index.ts' export default class extends ExtensionBase { override name = 'invite' @@ -80,7 +81,7 @@ export default class extends ExtensionBase { case 'create_acc':{ if (ctx.data) { - let form: {invite_code?: string, username?: string, password?: string} = ctx.data.form + let form: {invite_code?: string, username?: string, password?: string, minecraft_name?: string} = ctx.data.form if (form.username && form.password && form.invite_code) { form.username = form.username.substring(0, 32) @@ -102,6 +103,14 @@ export default class extends ExtensionBase { }) .where('code', form.invite_code) .then() + if (form.minecraft_name)//If a minecraft name was entered + { + await knex + .query('_minecraft_minecraft') + // @ts-expect-error + .insert({minecraft_name: form.minecraft_name, user_id: id}) + } + return this.return_html(ctx, 'login', undefined, 500, 303, { "Location": "/", "Set-Cookie": this.set_cookie('auth', 'Basic '+auth, true) diff --git a/src/extensions/invite/register.html b/src/extensions/invite/register.html index 83c39f0..e30ec71 100644 --- a/src/extensions/invite/register.html +++ b/src/extensions/invite/register.html @@ -17,6 +17,11 @@ + + + + + diff --git a/src/extensions/minecraft/index.html b/src/extensions/minecraft/index.html index c0d87d1..dd55a7d 100644 --- a/src/extensions/minecraft/index.html +++ b/src/extensions/minecraft/index.html @@ -1,4 +1,13 @@ {% extends "extension.html" %} {% block body %} + + + + + + +
+ + {{auth_err}} {% endblock %} diff --git a/src/extensions/minecraft/index.ts b/src/extensions/minecraft/index.ts index adbe5b5..036ea9b 100644 --- a/src/extensions/minecraft/index.ts +++ b/src/extensions/minecraft/index.ts @@ -1,21 +1,60 @@ import { ExtensionBase } from "../../modules.js" +import Knex from "../../modules/knex.ts" +import { unpack } from "../../util.ts" export default class extends ExtensionBase { override name = 'minecraft' override title = 'Minecraft' + override tables = true override init: Extension['init'] = async (context) => { return ExtensionBase.init(this, context) } - override handle: Extension['handle'] = (ctx) => { + override handle: Extension['handle'] = async (ctx) => { var location = ctx.path.shift() + let [knex]: [Knex] = this.get_dependencies('Knex') switch (location) { case '': case undefined: { + let [name, err] = await knex + .query('_minecraft') + .select('minecraft_name') + .where('user_id', ctx.context.user?.id) + .first() + .then(unpack) + + if (name) + ctx.context.minecraft_username = name.minecraft_name + else + ctx.context.minecraft_username = "" return this.return_html(ctx, 'index') } + case 'change':{ + if (ctx.data) + { + let new_MCName = ctx.data.form.minecraft_name + let [name, err] = await knex + .query('_minecraft') + .select('minecraft_name') + .where('user_id', ctx.context.user?.id) + + if (name){ + await knex + .query('_minecraft') + .update('minecraft_name', new_MCName) + .where('user_id', ctx.context.user?.id) + }else{ + await knex + .query('_minecraft') + // @ts-expect-error + .insert({minecraft_name: new_MCName, user_id: ctx.context.user?.id}) + } + } + + return this.return(ctx, undefined, location='/minecraft') + } } } } diff --git a/src/extensions/minecraft/tables.ts b/src/extensions/minecraft/tables.ts new file mode 100644 index 0000000..a9e8b1a --- /dev/null +++ b/src/extensions/minecraft/tables.ts @@ -0,0 +1,26 @@ +import { MigrationMap, Tables, VersionMap } from '../../classes/tables.ts' +import { Knex } from '../../modules.ts' + +export default class extends Tables { + override versions(versions: VersionMap) { + versions.set('minecraft', 0) + + return versions + } + + override migrations(knex: Knex, migrations: MigrationMap) { + migrations.set('minecraft', { + 0: async ()=>{ + await knex.schema() + .createTable('_minecraft', (table) => { + table.increments('id').primary() + table.string('minecraft_name').notNullable() + table.integer('user_id') + table.foreign('user_id', 'fk_user_id').references('_root_user.id') + }) + }, + }) + + return migrations + } +}