Added dependency injection

This commit is contained in:
peter
2020-01-22 00:58:12 +01:00
parent 030908376b
commit e4a6972884
27 changed files with 1769 additions and 208 deletions
+1259 -40
View File
File diff suppressed because it is too large Load Diff
+8 -2
View File
@@ -4,8 +4,9 @@
"version": "1.0.0", "version": "1.0.0",
"scripts": { "scripts": {
"tsc": "tsc", "tsc": "tsc",
"start": "npm run build; node lib/Launcher.js", "start": "npm run build; node lib/src/Launcher.js",
"build": "npm run clean; npm run build-backend; npm run build-frontend", "build": "npm run clean; npm run build-backend; npm run build-frontend",
"test": "npm run clean && npm run build-backend && mocha lib/test/backendTest.js",
"build-backend": "tsc;", "build-backend": "tsc;",
"build-frontend": "mkdir dist; mkdir dist/static; npm run build-dashboard;", "build-frontend": "mkdir dist; mkdir dist/static; npm run build-dashboard;",
"build-dashboard": "cd src/frontend; npm i && npm run build; cp -r dist/* ../../dist/static", "build-dashboard": "cd src/frontend; npm i && npm run build; cp -r dist/* ../../dist/static",
@@ -20,6 +21,7 @@
"author": "frontblock.me", "author": "frontblock.me",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@types/mocha": "^5.2.7",
"bsert": "0.0.10", "bsert": "0.0.10",
"bsock": "^0.1.9", "bsock": "^0.1.9",
"child-process-promise": "^2.2.1", "child-process-promise": "^2.2.1",
@@ -34,14 +36,17 @@
"loadson": "^1.0.0", "loadson": "^1.0.0",
"log4js": "^4.5.1", "log4js": "^4.5.1",
"lowdb": "^1.0.0", "lowdb": "^1.0.0",
"mocha": "^7.0.0",
"node-fetch": "^2.6.0", "node-fetch": "^2.6.0",
"path": "^0.12.7", "path": "^0.12.7",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.0", "rimraf": "^3.0.0",
"rpclibrary": "^1.5.2", "rpclibrary": "^1.6.2",
"simple-git": "^1.124.0", "simple-git": "^1.124.0",
"spawn-sync": "^2.0.0", "spawn-sync": "^2.0.0",
"sqlite3": "^4.1.0", "sqlite3": "^4.1.0",
"trash": "^6.0.0", "trash": "^6.0.0",
"tsyringe": "^4.0.1",
"upgiter": "^1.0.4", "upgiter": "^1.0.4",
"uuid": "^3.3.3", "uuid": "^3.3.3",
"xml2js": "^0.4.22" "xml2js": "^0.4.22"
@@ -50,6 +55,7 @@
"@types/express": "^4.17.0", "@types/express": "^4.17.0",
"@types/node": "^11.13.19", "@types/node": "^11.13.19",
"@types/semver": "^6.0.1", "@types/semver": "^6.0.1",
"madge": "^3.6.0",
"terser-webpack-plugin": "^1.4.1", "terser-webpack-plugin": "^1.4.1",
"ts-loader": "^5.3.3", "ts-loader": "^5.3.3",
"typescript": "^3.5.3", "typescript": "^3.5.3",
+30 -14
View File
@@ -3,19 +3,37 @@
import { getLogger } from 'frontblock-generic/Types'; import { getLogger } from 'frontblock-generic/Types';
import { promises as fs, mkdirSync } from "fs" import { promises as fs, mkdirSync } from "fs"
import { RPCServer } from 'rpclibrary' import { RPCServer } from 'rpclibrary'
import { AdminConf, TableDefiniton } from '../Types/Types';
import { RPCConfigLoader } from '../Components/RPCConfigLoader';
import * as Path from 'path' import * as Path from 'path'
import * as Knex from 'knex'; import * as Knex from 'knex';
import * as http from 'http'; import * as http from 'http';
import * as express from 'express'; import * as express from 'express';
import { GuildManager } from '../Components/Guild/GuildManager';
import { ItemManager } from '../Components/Item/ItemManager';
import { RaidManager } from '../Components/Raid/RaidManager';
import { CharacterManager } from '../Components/User/CharacterManager';
import { LoginManager } from '../Components/User/LoginManager';
import { RootComponent } from '../Injector/ServiceDecorator';
import { TableDefinitionExporter } from '../Types/Interfaces'; import { TableDefinitionExporter } from '../Types/Interfaces';
import { AdminConf, TableDefiniton } from '../Types/Types';
import { RPCConfigLoader } from '../Components/RPCConfigLoader';
import { FrontworkComponent } from '../Types/FrontworkComponent'; import { FrontworkComponent } from '../Types/FrontworkComponent';
import { IAdmin } from './Interface';
const logger = getLogger("admin", 'debug') const logger = getLogger("admin", 'debug')
@RootComponent({
rootInterface: IAdmin,
imports: [
GuildManager,
ItemManager,
RaidManager,
CharacterManager,
LoginManager
]
})
export class FrontworkAdmin export class FrontworkAdmin
implements TableDefinitionExporter { implements TableDefinitionExporter, IAdmin {
knex:Knex knex:Knex
config: RPCConfigLoader<AdminConf> config: RPCConfigLoader<AdminConf>
rpcServer: RPCServer rpcServer: RPCServer
@@ -23,7 +41,7 @@ implements TableDefinitionExporter {
private express private express
private httpServer private httpServer
constructor(private components: FrontworkComponent[]){ constructor(private frontworkComponents: FrontworkComponent[] = []){
this.config = new RPCConfigLoader<AdminConf>({ this.config = new RPCConfigLoader<AdminConf>({
name: "FrontworkAdminConf", name: "FrontworkAdminConf",
getDefaultConfig: () => { getDefaultConfig: () => {
@@ -40,19 +58,17 @@ implements TableDefinitionExporter {
} }
} }
}, './config', this.configChangeHandler) }, './config', this.configChangeHandler)
components.forEach(c => {
c.admin = this
if(c.onSetAdmin) c.onSetAdmin(this)
})
} }
async start(){ async start(){
await this.makeKnex() await this.makeKnex()
this.startWebsocket() this.startWebsocket()
await Promise.all( this.components.map(c => c.initialize?c.initialize():undefined )) await Promise.all( this.frontworkComponents.map(c => c.initialize?c.initialize():undefined ))
this.startWebserver() this.startWebserver()
}
stop(){
process.exit(0);
} }
protected configChangeHandler = (conf:AdminConf, key?:string) => { protected configChangeHandler = (conf:AdminConf, key?:string) => {
@@ -71,13 +87,13 @@ implements TableDefinitionExporter {
getTableDefinitions(): TableDefiniton[]{ getTableDefinitions(): TableDefiniton[]{
return [ return [
...this.components ...this.frontworkComponents
].flatMap(exporter => exporter.getTableDefinitions()) ].flatMap(exporter => exporter.getTableDefinitions())
} }
private startWebsocket(){ private startWebsocket(){
this.rpcServer = new RPCServer(20000, [ this.rpcServer = new RPCServer(20000, [
...this.components, ...this.frontworkComponents,
]) ])
} }
@@ -169,4 +185,4 @@ process.on( 'SIGINT', function() {
logger.info("Shutting down from SIGINT (Ctrl-C)" ); logger.info("Shutting down from SIGINT (Ctrl-C)" );
// some other closing procedures go here // some other closing procedures go here
process.exit(0); process.exit(0);
}) })
+13
View File
@@ -0,0 +1,13 @@
import { RPCConfigLoader } from "../Components/RPCConfigLoader"
import { AdminConf } from "../Types/Types"
import { RPCServer } from "rpclibrary"
import Knex = require("knex")
export class IAdmin{
knex: Knex
config: RPCConfigLoader<AdminConf>
rpcServer: RPCServer
start: ()=>Promise<void>
stop: ()=>void
}
+9 -4
View File
@@ -1,8 +1,10 @@
import { ConfigLoader } from "loadson";
import { Inject, Module } from "../../Injector/ServiceDecorator";
import { GuildManagerFeatureIfc, GuildManagerIfc } from "./RPCInterface";
import { FrontworkAdmin } from "../../Admin/Admin"; import { FrontworkAdmin } from "../../Admin/Admin";
import { FrontworkComponent } from "../../Types/FrontworkComponent"; import { FrontworkComponent } from "../../Types/FrontworkComponent";
import { _Rank, Rank } from "../../Types/Types"; import { _Rank, Rank } from "../../Types/Types";
import { ConfigLoader } from "loadson"; import { IAdmin } from "../../Admin/Interface";
import { GuildManagerIfc, GuildManagerFeatureIfc } from "./RPCInterface";
export type Guild = { export type Guild = {
name: string name: string
@@ -10,11 +12,14 @@ export type Guild = {
description: string description: string
} }
@Module()
export class GuildManager export class GuildManager
implements FrontworkComponent<GuildManagerIfc, GuildManagerFeatureIfc>{ implements FrontworkComponent<GuildManagerIfc, GuildManagerFeatureIfc>{
name = "GuildManager" as "GuildManager"; name = "GuildManager" as "GuildManager";
admin: FrontworkAdmin
@Inject(IAdmin)
private admin
guild: ConfigLoader<Guild> guild: ConfigLoader<Guild>
@@ -56,7 +61,7 @@ implements FrontworkComponent<GuildManagerIfc, GuildManagerFeatureIfc>{
getTableDefinitions = () => [] getTableDefinitions = () => []
headCount = async () => await Promise.all( headCount = async () => await Promise.all(
['ADMIN', ..._Rank].map(async r => { _Rank.map(async r => {
const res = await this.admin.knex const res = await this.admin.knex
.select('*') .select('*')
.from('users') .from('users')
+18 -10
View File
@@ -1,16 +1,17 @@
import { TableDefinitionExporter } from "../../Types/Interfaces";
import { TableDefiniton, _Rank } from "../../Types/Types";
import { FrontworkAdmin } from "../../Admin/Admin";
import { T1 } from "../../Types/Items"; import { T1 } from "../../Types/Items";
import { RPC } from "rpclibrary";
import { Inject, Module } from "../../Injector/ServiceDecorator";
import { ItemManagerFeatureIfc, ItemManagerIfc } from "./RPCInterface";
import { FrontworkComponent } from "../../Types/FrontworkComponent"; import { FrontworkComponent } from "../../Types/FrontworkComponent";
import { RPC, RPCInterface } from "rpclibrary"; import { TableDefinitionExporter } from "../../Types/Interfaces";
import { ItemManagerIfc, ItemManagerFeatureIfc } from "./RPCInterface"; import { FrontworkAdmin } from "../../Admin/Admin";
import { TableDefiniton } from "../../Types/Types";
import { IAdmin } from "../../Admin/Interface";
const fetch = require('node-fetch') const fetch = require('node-fetch')
const xml2js = require('xml2js'); const xml2js = require('xml2js');
const parser = new xml2js.Parser(/* options */); const parser = new xml2js.Parser(/* options */);
export type Item = { export type Item = {
id?:number id?:number
name:string name:string
@@ -20,12 +21,14 @@ export type Item = {
hidden:boolean hidden:boolean
} }
@Module()
export class ItemManager export class ItemManager
implements FrontworkComponent<ItemManagerIfc, ItemManagerFeatureIfc>, TableDefinitionExporter{ implements FrontworkComponent<ItemManagerIfc, ItemManagerFeatureIfc>, TableDefinitionExporter{
admin:FrontworkAdmin
name = "ItemManager" as "ItemManager"; name = "ItemManager" as "ItemManager";
@Inject(IAdmin)
private admin: FrontworkAdmin
exportRPCs(): RPC<any, any>[]{ exportRPCs(): RPC<any, any>[]{
return [{ return [{
name: 'getItems', name: 'getItems',
@@ -90,7 +93,11 @@ implements FrontworkComponent<ItemManagerIfc, ItemManagerFeatureIfc>, TableDefin
return <number>count[0]['count(*)'] return <number>count[0]['count(*)']
} }
initialize = async() => { private initialized = false
initialize = async () => {
if(!this.initialized)
this.initialized = true
const allItems = [...T1] const allItems = [...T1]
const countCache = await this.countItems() const countCache = await this.countItems()
if(countCache != allItems.length){ if(countCache != allItems.length){
@@ -100,6 +107,7 @@ implements FrontworkComponent<ItemManagerIfc, ItemManagerFeatureIfc>, TableDefin
.knex('items') .knex('items')
.insert(items) .insert(items)
}catch(e){ }catch(e){
console.log(e)
console.info("Skipping item insertion") console.info("Skipping item insertion")
} }
} }
+6 -3
View File
@@ -1,7 +1,10 @@
import { Raid, Signup, User } from "../../Types/Types"; import { Raid, Signup, User } from "../../Types/Types"
import { RPCInterface } from "rpclibrary";
export type RaidManagerIfc = RPCInterface export type RaidManagerIfc = {
RaidManager:{
getRaids: () => Promise<Raid[]>
}
}
export type RaidManagerFeatureIfc = { export type RaidManagerFeatureIfc = {
manageRaid: { manageRaid: {
+17 -11
View File
@@ -1,14 +1,22 @@
import { TableDefiniton, User, _Rank, Raid, Signup } from "../../Types/Types"; import { Inject, Module } from "../../Injector/ServiceDecorator";
import { FrontworkAdmin } from "../../Admin/Admin"; import { RaidManagerIfc, RaidManagerFeatureIfc } from "./RPCInterface";
import { FrontworkComponent } from "../../Types/FrontworkComponent"; import { FrontworkComponent } from "../../Types/FrontworkComponent";
import { RaidManagerFeatureIfc, RaidManagerIfc } from "./RPCInterface"; import { FrontworkAdmin } from "../../Admin/Admin";
import { TableDefiniton, Signup, Raid, User } from "../../Types/Types";
import { IAdmin } from "../../Admin/Interface";
@Module()
export class RaidManager export class RaidManager
implements FrontworkComponent<RaidManagerIfc, RaidManagerFeatureIfc>{ implements FrontworkComponent<RaidManagerIfc, RaidManagerFeatureIfc>{
name = "RaidManager" as "RaidManager"; name = "RaidManager" as "RaidManager";
admin: FrontworkAdmin
exportRPCs = () => [] @Inject(IAdmin)
private admin: FrontworkAdmin
exportRPCs = () => [{
name: 'getRaids' as 'getRaids',
call: this.getRaids
},]
exportRPCFeatures() { exportRPCFeatures() {
return [{ return [{
@@ -26,9 +34,6 @@ implements FrontworkComponent<RaidManagerIfc, RaidManagerFeatureIfc>{
},{ },{
name: 'signup' as 'signup', name: 'signup' as 'signup',
exportRPCs: () => [{ exportRPCs: () => [{
name: 'getRaids' as 'getRaids',
call: this.getRaids
},{
name: 'getSingups' as 'getSingups', name: 'getSingups' as 'getSingups',
call: this.getSignups call: this.getSignups
},{ },{
@@ -52,9 +57,10 @@ implements FrontworkComponent<RaidManagerIfc, RaidManagerFeatureIfc>{
},{ },{
name: 'signups', name: 'signups',
tableBuilder: (table) => { tableBuilder: (table) => {
table.integer('raid_id').primary() table.primary(['raid_id', 'user_id'])
table.integer('raid_id')
table.foreign('raid_id').references('id').inTable('raids') table.foreign('raid_id').references('id').inTable('raids')
table.integer('user_id').primary() table.integer('user_id')
table.foreign('user_id').references('id').inTable('users') table.foreign('user_id').references('id').inTable('users')
} }
} }
@@ -77,7 +83,7 @@ implements FrontworkComponent<RaidManagerIfc, RaidManagerFeatureIfc>{
}) })
.delete() .delete()
getRaids = async () => await this.admin.knex getRaids = async () : Promise<Raid[]> => await this.admin.knex
.select('*') .select('*')
.from('raids') .from('raids')
@@ -0,0 +1,96 @@
import { RPCInterface } from "rpclibrary";
import { Inject, Module } from "../../Injector/ServiceDecorator";
import { TableDefiniton, Character } from "../../Types/Types";
import { CharacterManagerIfc } from "./RPCInterface";
import { FrontworkComponent } from "../../Types/FrontworkComponent";
import { FrontworkAdmin } from "../../Admin/Admin";
import { LoginManager } from "./LoginManager";
import { getSpecTableData, SpecT } from "../../Types/PlayerSpecs";
import { IAdmin } from "../../Admin/Interface";
@Module()
export class CharacterManager
implements FrontworkComponent<CharacterManagerIfc, RPCInterface>{
name = "CharacterManager" as "CharacterManager";
@Inject(IAdmin)
private admin: IAdmin
private loginManager : LoginManager
exportRPCs = () => [
{
name: 'getSpecId' as 'getSpecId',
call: this.getSpecId
}
]
exportRPCFeatures = () => [
{
name: "createCharacter",
exportRPCs: () => [
{
name: 'createCharacter',
call: this.createCharacter
}
]
}
]
getTableDefinitions = (): TableDefiniton[] => [
{
name: 'characters',
tableBuilder: (table) => {
table.increments("id").primary()
table.string("name").notNullable().unique()
table.integer("specid").notNullable()
table.foreign("specid").references("specs.id")
table.integer("userid").notNullable()
table.foreign("userid").references("users.id")
}
},{
name: 'specs',
tableBuilder: (table) => {
table.increments("id")
table.string('class')
table.string('name')
table.unique(['class', 'name'])
}
}
]
private initialized = false
initialize = async () => {
if(!this.initialized)
this.initialized = true
//initialize spec table
await this.admin.knex('specs').insert(getSpecTableData()).catch(e => { console.log("skipping spec insertion") })
}
createCharacter = async (userToken: string, character : Character) : Promise<Character> => {
try{
await this.admin.knex('characters').insert(character)
const char : Character = await this.admin.knex.select('*').from('characters').where(character).first()
return char
}catch(e){
console.log(e);
}
throw new Error('Unable to create character')
}
getCharacters = async() : Promise<Character[]> => {
return this.admin.knex.select('*').from('characters')
}
getSpecId = async <c extends keyof SpecT>(clazz: c, name: SpecT[c]) => await this.admin.knex
.from('specs')
.select('id')
.where({
class: clazz,
name: name
}).first().then(spec => spec.id)
}
+78 -51
View File
@@ -1,10 +1,15 @@
import { RPCServer, Socket } from "rpclibrary"; import { RPCServer, Socket } from "rpclibrary";
import { TableDefiniton, AnyRPCExporter, User, RPCPermission, Token, Auth, Rank, FrontcraftFeatureIfc, _Rank } from "../../Types/Types"; import { Inject, Module } from "../../Injector/ServiceDecorator";
import { FrontworkAdmin } from "../../Admin/Admin"; import { FrontworkAdmin } from "../../Admin/Admin";
import { PrivilegedRPCExporter } from "../../Types/PrivilegedRPCExporter"; import { GuildManager } from "../Guild/GuildManager";
import { FrontworkComponent } from "../../Types/FrontworkComponent"; import { ItemManager } from "../Item/ItemManager";
import { getSpecTableData } from "../../Types/PlayerSpecs" import { RaidManager } from "../Raid/RaidManager";
import { CharacterManager } from "./CharacterManager";
import { LoginManagerIfc, LoginManagerFeatureIfc } from "./RPCInterface"; import { LoginManagerIfc, LoginManagerFeatureIfc } from "./RPCInterface";
import { FrontworkComponent } from "../../Types/FrontworkComponent";
import { Rank, User, Auth, _Rank, TableDefiniton, RPCPermission, FrontcraftFeatureIfc, AnyRPCExporter, Token } from "../../Types/Types";
import { IAdmin } from "../../Admin/Interface";
const uuid = require('uuid/v4') const uuid = require('uuid/v4')
const ONE_WEEK = 604800000 const ONE_WEEK = 604800000
@@ -15,21 +20,35 @@ type Serverstate = {
allowed: string[] allowed: string[]
} }
@Module()
export class LoginManager export class LoginManager
implements FrontworkComponent<LoginManagerIfc, LoginManagerFeatureIfc>{ implements FrontworkComponent<LoginManagerIfc, LoginManagerFeatureIfc>{
name = "Authenticator" as "Authenticator"; name = "Authenticator" as "Authenticator";
admin:FrontworkAdmin
@Inject(IAdmin)
private admin: FrontworkAdmin
@Inject(GuildManager)
private guild : GuildManager
@Inject(ItemManager)
private item : ItemManager
@Inject(RaidManager)
private raid : RaidManager
@Inject(CharacterManager)
private character : CharacterManager
exporters :any[] = []
rankServers : {[rank in Rank] : Serverstate} rankServers : {[rank in Rank] : Serverstate}
userLogins : {[username in string] : { userLogins : {[username in string] : {
user: User
connections: {[port in number]: Socket} connections: {[port in number]: Socket}
auth: Auth auth: Auth
}} = {} }} = {}
constructor(
private exporters: PrivilegedRPCExporter[]
){}
exportRPCs = () => [ exportRPCs = () => [
{ {
name: 'login' as 'login', name: 'login' as 'login',
@@ -43,21 +62,14 @@ implements FrontworkComponent<LoginManagerIfc, LoginManagerFeatureIfc>{
},{ },{
name: 'checkToken' as 'checkToken', name: 'checkToken' as 'checkToken',
call: async (tokenValue : string, rank: Rank) => this.checkToken(tokenValue, rank) call: async (tokenValue : string, rank: Rank) => this.checkToken(tokenValue, rank)
},{
name: 'createUser' as 'createUser',
call: this.createUser
} }
] ]
onSetAdmin(admin:FrontworkAdmin){
this.exporters.forEach(e => e['admin'] = admin)
}
exportRPCFeatures = () => [ exportRPCFeatures = () => [
{ {
name: 'createUser' as 'createUser',
exportRPCs: () => [{
name: 'createUser' as 'createUser',
call: this.createUser
}]
},{
name: 'modifyPermissions' as 'modifyPermissions', name: 'modifyPermissions' as 'modifyPermissions',
exportRPCs: () => [{ exportRPCs: () => [{
name: 'getPermissions' as 'getPermissions', name: 'getPermissions' as 'getPermissions',
@@ -79,36 +91,26 @@ implements FrontworkComponent<LoginManagerIfc, LoginManagerFeatureIfc>{
table.string("pwhash").notNullable() table.string("pwhash").notNullable()
table.string("rank").notNullable() table.string("rank").notNullable()
table.string("email").nullable().unique() table.string("email").nullable().unique()
} table.boolean("locked").defaultTo(true)
},{
name: 'characters',
tableBuilder: (table) => {
table.increments("id").primary()
table.string("name").notNullable().unique()
table.integer("specid").notNullable()
table.foreign("specid").references("specs.id")
table.integer("userid").notNullable()
table.foreign("userid").references("users.id")
} }
},{ },{
name: 'rpcpermissions', name: 'rpcpermissions',
tableBuilder: (table) => { tableBuilder: (table) => {
table.string("name").primary().notNullable() table.string("name").primary().notNullable()
table.boolean("ADMIN").defaultTo(true).notNullable() _Rank.forEach(r => {
_Rank.forEach(r => table.boolean(r).defaultTo(false).notNullable()) if(r === 'ADMIN')
} table.boolean(r).defaultTo(true).notNullable()
},{ else
name: 'specs', table.boolean(r).defaultTo(false).notNullable()
tableBuilder: (table) => { })
table.increments("id")
table.string('class')
table.string('name')
} }
} }
,...this.exporters.flatMap(exp => exp['getTableDefinitions']?exp['getTableDefinitions']():undefined) ,...this.exporters.flatMap(exp => exp['getTableDefinitions']?exp['getTableDefinitions']():undefined)
] ]
initialize = async () => { initialize = async () => {
this.exporters = [this.guild, this.item, this.raid, this.character]
//set up permissions //set up permissions
await Promise.all( await Promise.all(
[this, ...this.exporters].flatMap(exp => exp.exportRPCFeatures().map(async (feature) => { [this, ...this.exporters].flatMap(exp => exp.exportRPCFeatures().map(async (feature) => {
@@ -117,12 +119,6 @@ implements FrontworkComponent<LoginManagerIfc, LoginManagerFeatureIfc>{
}catch(e){} }catch(e){}
}))) })))
//initialize managed exporters
await Promise.all(this.exporters.map(ex => ex['initialize']?ex['initialize']():undefined))
//initialize spec table
await this.admin.knex('specs').insert(getSpecTableData()).catch(e => { console.log("skipping spec insertion") })
//start rankServers //start rankServers
const rankServers : any = {} const rankServers : any = {}
@@ -138,7 +134,7 @@ implements FrontworkComponent<LoginManagerIfc, LoginManagerFeatureIfc>{
} }
this.rankServers = rankServers this.rankServers = rankServers
setInterval(this.checkExpiredSessions, 600_000) setInterval(this.checkExpiredSessions, 600_000)
} }
checkExpiredSessions = () => { checkExpiredSessions = () => {
@@ -151,8 +147,14 @@ implements FrontworkComponent<LoginManagerIfc, LoginManagerFeatureIfc>{
} }
checkConnection = async (socket: Socket) => { checkConnection = async (socket: Socket) => {
let data : Auth | false = false let data : any = false
let tries = 0
while(!data){ while(!data){
tries ++
if(tries === 5){
socket.destroy()
return
}
data = await Promise.race([socket.call('getUserData'), new Promise((res, rej) => { setTimeout(res, 250);})]) data = await Promise.race([socket.call('getUserData'), new Promise((res, rej) => { setTimeout(res, 250);})])
} }
this.userLogins[data.user.name].connections[socket.port] = socket this.userLogins[data.user.name].connections[socket.port] = socket
@@ -181,20 +183,40 @@ implements FrontworkComponent<LoginManagerIfc, LoginManagerFeatureIfc>{
} }
getRPCForRank = async (rank: Rank): Promise<AnyRPCExporter[]> => { getRPCForRank = async (rank: Rank): Promise<AnyRPCExporter[]> => {
return [ let rpcs = [
...this.exportRPCFeatures(), ...this.exportRPCFeatures(),
...this.exporters.flatMap((exp) => exp.exportRPCFeatures()) ...this.exporters.flatMap((exp) => exp.exportRPCFeatures())
].filter(async (feature) => await this.getPermission(<keyof FrontcraftFeatureIfc> feature.name, rank)) ]
const bits = await Promise.all(rpcs.map(async (feature) => {
const allowed = await this.getPermission(<keyof FrontcraftFeatureIfc> feature.name, rank)
return allowed
}))
return rpcs.filter(entry => bits.shift())
} }
createUser = async(user:User): Promise<User> => { createUser = async(user:User): Promise<User> => {
if(user.rank === 'ADMIN'){
const admins = await this.admin.knex
.select("*")
.from('users')
.where({rank: 'ADMIN'})
if(admins.length > 0){
return {} as User
}
user.locked = false
}
await this.admin.knex('users') await this.admin.knex('users')
.insert(user) .insert(user)
const users = await this.admin.knex const users = await this.admin.knex
.select("*") .select("*")
.from('users') .from('users')
.where(user) .where(user)
return users[0] return users[0]
} }
@@ -238,7 +260,7 @@ implements FrontworkComponent<LoginManagerIfc, LoginManagerFeatureIfc>{
port: this.rankServers[user.rank].port port: this.rankServers[user.rank].port
} }
this.userLogins[user.name] = {connections: {}, auth: userAuth} this.userLogins[user.name] = {connections: {}, auth: userAuth, user:user}
this.rankServers[user.rank].allowed.push(token.value) this.rankServers[user.rank].allowed.push(token.value)
return userAuth return userAuth
@@ -247,8 +269,13 @@ implements FrontworkComponent<LoginManagerIfc, LoginManagerFeatureIfc>{
throw new Error('login failed') throw new Error('login failed')
} }
getUserRecordByToken(tokenValue: string){
const maybeRecord = Object.values(this.userLogins).find(login => login.auth.token.value === tokenValue)
return maybeRecord?maybeRecord:undefined
}
getAuth = async (tokenValue:string) : Promise<Auth> => { getAuth = async (tokenValue:string) : Promise<Auth> => {
const maybeAuth = Object.values(this.userLogins).find(login => login.auth.token.value === tokenValue) const maybeAuth = this.getUserRecordByToken(tokenValue)
if(maybeAuth) if(maybeAuth)
return maybeAuth.auth return maybeAuth.auth
+16 -4
View File
@@ -1,4 +1,5 @@
import { Token, Auth, User, RPCPermission, Rank } from "../../Types/Types" import { Auth, Rank, User, RPCPermission, Character } from "../../Types/Types"
import { SpecT } from "../../Types/PlayerSpecs"
export type LoginManagerIfc = { export type LoginManagerIfc = {
Authenticator: { Authenticator: {
@@ -6,15 +7,26 @@ export type LoginManagerIfc = {
logout: (username: string, tokenValue :string) => Promise<void> logout: (username: string, tokenValue :string) => Promise<void>
getAuth: (tokenValue: string) => Promise<Auth> getAuth: (tokenValue: string) => Promise<Auth>
checkToken: (token: string, rank: Rank) => Promise<boolean> checkToken: (token: string, rank: Rank) => Promise<boolean>
createUser: (user:User) => Promise<User>
} }
} }
export type LoginManagerFeatureIfc = { export type LoginManagerFeatureIfc = {
createUser: {
createUser: (user:User) => Promise<User>
}
modifyPermissions: { modifyPermissions: {
setPermission: (perm: RPCPermission) => Promise<void> setPermission: (perm: RPCPermission) => Promise<void>
getPermissions: () => Promise<RPCPermission[]> getPermissions: () => Promise<RPCPermission[]>
} }
}
export type CharacterManagerIfc = {
CharacterManager: {
getSpecId : <c extends keyof SpecT>(clazz: c, name: SpecT[c]) => Promise<number>
getCharacters : () => Promise<Character[]>
}
}
export type CharacterManagerFeatureIfc = {
createCharacter: {
createCharacter: (usertoken: string, char : Character) => Promise<Character>
}
} }
+50
View File
@@ -0,0 +1,50 @@
import 'reflect-metadata';
import { Type } from './Util';
import { FrontworkComponent } from '../Types/FrontworkComponent';
/**
* The Injector stores services and resolves requested instances.
*/
export const Injector = new class {
injectionQueue :any[] = []
rootInterface : Type<any>
root : Type<any>
rootModules : Type<any>[] = []
moduleObjs : {[key in string] : FrontworkComponent} = {}
/**
* Resolves instances by injecting required services
* @param {Type<any>} target
* @returns {T}
*/
resolve<T>(target: Type<any>): T {
// tokens are required dependencies, while injections are resolved tokens from the Injector
if(this.moduleObjs[target.name])
return this.moduleObjs[target.name] as any
if(target.name === this.rootInterface.name || target.name === this.root.name){
let modules = this.rootModules.map(m => {
const module = new m()
this.moduleObjs[m.name] = module
return module
})
const rootobj = new this.root(modules);
this.moduleObjs[this.rootInterface.name] = rootobj
this.moduleObjs[target.name] = rootobj
this.injectionQueue.forEach(i => {
i.target[i.where] = this.moduleObjs[i.what.name]
})
return rootobj
}
this.moduleObjs[target.name] = new target()
return this.moduleObjs[target.name] as any
}
};
+34
View File
@@ -0,0 +1,34 @@
import { Injector } from "./Injector";
import { Type, GenericClassDecorator } from "./Util";
import { FrontworkComponent } from "../Types/FrontworkComponent";
/**
* @returns {GenericClassDecorator<Type<any>>}
* @constructor
*/
export const Module = (...args) : GenericClassDecorator<Type<any>> => {
return (target: Type<any>) => {
Injector.rootModules.push(target)
}
}
/**
* @returns {GenericClassDecorator<Type<any>>}
* @constructor
*/
export const RootComponent = (config : {
rootInterface : Type<any>
imports : Type<FrontworkComponent>[]
}) : GenericClassDecorator<Type<any>> => {
return (target: Type<any>) => {
Injector.rootModules = config.imports
Injector.rootInterface = config.rootInterface
Injector.root = target
}
}
export const Inject = (type: any) => {
return function (_this, key) {
Injector.injectionQueue.push({what: type, target: _this, where:key})
}
}
+11
View File
@@ -0,0 +1,11 @@
/**
* Type for what object is instances of
*/
export interface Type<T> {
new(...args: any[]): T;
}
/**
* Generic `ClassDecorator` type
*/
export type GenericClassDecorator<T> = (target: T) => void;
+3 -21
View File
@@ -1,23 +1,5 @@
import { FrontworkAdmin } from './Admin/Admin' import { Injector } from './Injector/Injector';
import { RaidManager } from "./Components/Raid/RaidManager"; import { IAdmin } from './Admin/Interface';
import { ItemManager } from "./Components/Item/ItemManager";
import { LoginManager } from "./Components/User/LoginManager";
import { Debugger } from './Components/Debugger/Debugger';
import { FrontworkComponent } from './Types/FrontworkComponent';
import { GuildManager } from './Components/Guild/GuildManager';
require('events').EventEmitter.defaultMaxListeners = 0; require('events').EventEmitter.defaultMaxListeners = 0;
let raidManager = new RaidManager() Injector.resolve<IAdmin>(IAdmin).start()
let itemManager = new ItemManager()
let guildManager = new GuildManager()
let loginManager = new LoginManager([
raidManager,
itemManager,
guildManager
])
let components:FrontworkComponent[] = [ guildManager, raidManager, itemManager, loginManager ]
let dbg = new Debugger(components)
new FrontworkAdmin([dbg, ...components]).start()
+1 -4
View File
@@ -1,7 +1,6 @@
import { PrivilegedRPCExporter } from "./PrivilegedRPCExporter";
import { RPCInterface, RPCExporter, RPCInterfaceArray } from "rpclibrary"; import { RPCInterface, RPCExporter, RPCInterfaceArray } from "rpclibrary";
import { PrivilegedRPCExporter } from "./PrivilegedRPCExporter";
import { TableDefinitionExporter } from "./Interfaces"; import { TableDefinitionExporter } from "./Interfaces";
import { FrontworkAdmin } from "../Admin/Admin";
import { TableDefiniton } from "./Types"; import { TableDefiniton } from "./Types";
export interface FrontworkComponent< export interface FrontworkComponent<
@@ -14,7 +13,6 @@ export interface FrontworkComponent<
PrivilegedRPCExporter<Ifc, FeatureIfc, Name, FeatureName, SubresT>, PrivilegedRPCExporter<Ifc, FeatureIfc, Name, FeatureName, SubresT>,
TableDefinitionExporter TableDefinitionExporter
{ {
admin:FrontworkAdmin
name: Name; name: Name;
exportRPCFeatures(): RPCExporter<FeatureIfc, FeatureName, SubresT>[] exportRPCFeatures(): RPCExporter<FeatureIfc, FeatureName, SubresT>[]
@@ -22,5 +20,4 @@ export interface FrontworkComponent<
getTableDefinitions(): TableDefiniton[] getTableDefinitions(): TableDefiniton[]
initialize?(): Promise<any> initialize?(): Promise<any>
onSetAdmin?(admin:FrontworkAdmin): void
} }
+14 -2
View File
@@ -1,6 +1,18 @@
import { Class, _Class, Spec } from "./Types"; import { Spec, _Class } from "./Types"
const specs : { [classname in Class] : string[] } = { export type SpecT = {
Warrior : 'Arms' | 'Fury' | 'Protection'
Rogue: 'Subetly' | 'Combat' | 'Assassination'
Hunter: 'Beast Mastery' | 'Marksmanship' | 'Survival'
Paladin: 'Holy' | 'Protection' | 'Retribution'
Priest: 'Discipline' | 'Holy' | 'Shadow'
Druid: 'Feral (Tank)' | 'Feral (DPS)' | 'Restoration' | 'Balance'
Mage: 'Frost' | 'Arcane' | 'Fire'
Warlock: 'Demonology' | 'Destruction' | 'Affliction'
Shaman: 'Restoration' | 'Enhancement' | 'Elemental'
}
export const specs : { [classname in keyof SpecT] : SpecT[classname][] } = {
Warrior : [ Warrior : [
'Arms', 'Arms',
'Fury', 'Fury',
+5 -4
View File
@@ -1,8 +1,9 @@
import { FrontworkAdmin } from "../Admin/Admin" import { RPCExporter, RPC } from "rpclibrary"
import { RPC, RPCExporter } from "rpclibrary"
import { TableDefiniton } from "./Types"
import { TableDefinitionExporter } from "./Interfaces"
import { ConfigExporter } from "loadson" import { ConfigExporter } from "loadson"
import { TableDefinitionExporter } from "./Interfaces"
import { FrontworkAdmin } from "../Admin/Admin"
import { TableDefiniton } from "./Types"
export abstract class Plugin<ConfType = {}> export abstract class Plugin<ConfType = {}>
implements ConfigExporter<ConfType>, RPCExporter<any,any,any>, TableDefinitionExporter{ implements ConfigExporter<ConfType>, RPCExporter<any,any,any>, TableDefinitionExporter{
+14 -6
View File
@@ -1,7 +1,7 @@
import * as Knex from "knex" import * as Knex from "knex"
import { RPCExporter } from "rpclibrary"; import { RPCExporter } from "rpclibrary";
import { RaidManagerFeatureIfc } from "../Components/Raid/RPCInterface"; import { RaidManagerIfc, RaidManagerFeatureIfc } from "../Components/Raid/RPCInterface";
import { LoginManagerIfc, LoginManagerFeatureIfc } from "../Components/User/RPCInterface"; import { LoginManagerIfc, CharacterManagerIfc, LoginManagerFeatureIfc, CharacterManagerFeatureIfc } from "../Components/User/RPCInterface";
export declare type NotificationSeverity = 'Info' | 'Important' | 'Error'; export declare type NotificationSeverity = 'Info' | 'Important' | 'Error';
@@ -34,9 +34,9 @@ export type User = {
id?: number id?: number
name: string name: string
pwhash: string pwhash: string
specid: number
rank: Rank rank: Rank
email?: string email?: string
locked: boolean
} }
export type Raid = { export type Raid = {
@@ -52,6 +52,13 @@ export type Signup = {
user_id: number user_id: number
} }
export type Character = {
id? : number
name : string
specid : number
userid : number
}
export type Token = { export type Token = {
value: string value: string
user_id: number user_id: number
@@ -60,12 +67,13 @@ export type Token = {
export type Auth = {port: number, user: User, token: Token} export type Auth = {port: number, user: User, token: Token}
export type FrontcraftIfc = LoginManagerIfc export type FrontcraftIfc = RaidManagerIfc
//& ItemManagerIfc & LoginManagerIfc
& CharacterManagerIfc
export type FrontcraftFeatureIfc = RaidManagerFeatureIfc export type FrontcraftFeatureIfc = RaidManagerFeatureIfc
& LoginManagerFeatureIfc & LoginManagerFeatureIfc
//& ItemManagerFeatureIfc & CharacterManagerFeatureIfc
export type Spec = { export type Spec = {
id?: number, id?: number,
+3 -3
View File
@@ -14269,9 +14269,9 @@
"integrity": "sha512-ZYzRkETgBrdEGzL5JSKimvjI2CX7ioyZCkX2BpcfyjqI+079W0wHAyj5W4rIZMcDSOHgLZtgz1IdDi/vU77KEQ==" "integrity": "sha512-ZYzRkETgBrdEGzL5JSKimvjI2CX7ioyZCkX2BpcfyjqI+079W0wHAyj5W4rIZMcDSOHgLZtgz1IdDi/vU77KEQ=="
}, },
"rpclibrary": { "rpclibrary": {
"version": "1.5.1", "version": "1.6.2",
"resolved": "https://registry.npmjs.org/rpclibrary/-/rpclibrary-1.5.1.tgz", "resolved": "https://registry.npmjs.org/rpclibrary/-/rpclibrary-1.6.2.tgz",
"integrity": "sha512-EN6wlifkFmEQrHhtbalS0i90ZBNbLNFcrQ8hTrcJLBuNMFab49R8i5fruoDLDJM88iPcx6eWccI6zLHK7Qkl2A==", "integrity": "sha512-lQTU4XkB9CSHz7YgtAcpVfyR5XrmTRX4P4eQjK6DDUjqKSFvJb5ChXjHnt1BcaNWbJ2VqmhCNVbcoyunJ2u7Rg==",
"requires": { "requires": {
"bsock": "^0.1.9", "bsock": "^0.1.9",
"http": "0.0.0", "http": "0.0.0",
+1 -1
View File
@@ -69,7 +69,7 @@
"normalize.css": "6.0.0", "normalize.css": "6.0.0",
"pace-js": "1.0.2", "pace-js": "1.0.2",
"roboto-fontface": "0.8.0", "roboto-fontface": "0.8.0",
"rpclibrary": "^1.5.1", "rpclibrary": "^1.6.2",
"rxjs": "6.5.2", "rxjs": "6.5.2",
"rxjs-compat": "6.3.0", "rxjs-compat": "6.3.0",
"socicon": "3.0.5", "socicon": "3.0.5",
+1 -3
View File
@@ -19,9 +19,7 @@ export class AppComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.analytics.trackPageViews(); this.analytics.trackPageViews();
this.loginSvc.getFeature('createUser').then( (f) => {
})
window['s'] = this.loginSvc window['s'] = this.loginSvc
} }
} }
@@ -1,6 +1,6 @@
<h1 id="title" class="title">Account application</h1> <h1 id="title" class="title">Account application</h1>
<form (ngSubmit)="register()" #form="ngForm" aria-labelledby="title"> <form (ngSubmit)="onSubmit()" #form="ngForm" aria-labelledby="title">
<div class="form-control-group"> <div class="form-control-group">
<label class="label" for="input-email">Email address:</label> <label class="label" for="input-email">Email address:</label>
@@ -36,7 +36,7 @@
<label class="label" for="input-password">Password:</label> <label class="label" for="input-password">Password:</label>
<input nbInput <input nbInput
fullWidth fullWidth
[(ngModel)]="user.password" [(ngModel)]="user.pwhash"
#password="ngModel" #password="ngModel"
name="password" name="password"
type="password" type="password"
@@ -58,14 +58,30 @@
<nb-card-body> <nb-card-body>
<nb-checkbox [(ngModel)]="showApplication" name="amMember" #checkbox>I am already a member</nb-checkbox> <nb-checkbox [(ngModel)]="showApplication" name="amMember" #checkbox>I am already a member</nb-checkbox>
<br /> <br />
<span *ngIf="showApplication"> <span *ngIf="showApplication">
And my main is &nbsp; <input name="preMember" nbInput> with rank <nb-select placeholder="Rank"> And my main is &nbsp; <input name="preMember" [(ngModel)]="character.name" nbInput>, a
<nb-option *ngFor="let rank of ranks" [value]=rank >{{rank}}</nb-option> <nb-select [(selected)]="character.spec" placeholder="Spec" (selectedChange)="onSelectSpec()">
<nb-option *ngFor="let spec of specs" [value]="spec">{{spec}}</nb-option>
</nb-select>
<nb-select [(selected)]="character.class" placeholder="Class" (selectedChange)="onSelectClass()">
<nb-option *ngFor="let class of classes" [value]="class">{{class}}</nb-option>
</nb-select>
with rank
<nb-select [(selected)]="user.rank" placeholder="Rank">
<nb-option *ngFor="let rank of ranks" [value]="rank" >{{rank}}</nb-option>
</nb-select> </nb-select>
</span> </span>
<span *ngIf="!showApplication">Hello my name is &nbsp; <input name="charName" nbInput>
I am playing a level 60 <input name="charName" placeholder="Spec + Class" nbInput> and I would like to join tranquil because <span *ngIf="!showApplication">Hello my name is &nbsp; <input name="charName" [(ngModel)]="character.name" nbInput>
I am playing a level 60
<nb-select [(selected)]="character.spec" placeholder="Spec" (selectedChange)="onSelectSpec()">
<nb-option *ngFor="let spec of specs" [value]="spec">{{spec}}</nb-option>
</nb-select>
<nb-select [(selected)]="character.class" placeholder="Class" (selectedChange)="onSelectClass()">
<nb-option *ngFor="let class of classes" [value]="class">{{class}}</nb-option>
</nb-select>
and I would like to join Tranquil because
<textarea nbInput fullWidth placeholder="reason" style="min-height: 400px"></textarea> <textarea nbInput fullWidth placeholder="reason" style="min-height: 400px"></textarea>
</span> </span>
@@ -79,7 +95,7 @@
size="giant" size="giant"
[disabled]="submitted || !form.valid" [disabled]="submitted || !form.valid"
[class.btn-pulse]="submitted"> [class.btn-pulse]="submitted">
Log In Register
</button> </button>
</form> </form>
@@ -1,7 +1,9 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { LoginApiService } from '../../services/login-api'; import { LoginApiService, hash } from '../../services/login-api';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { _Rank } from '../../../../../../backend/Types/Types' import { _Rank, _Class, Class, User } from '../../../../../../backend/Types/Types'
import { specs } from '../../../../../../backend/Types/PlayerSpecs'
@Component({ @Component({
selector: 'register', selector: 'register',
@@ -9,9 +11,22 @@ import { _Rank } from '../../../../../../backend/Types/Types'
}) })
export class RegisterComponent implements OnInit{ export class RegisterComponent implements OnInit{
user: any = {}; user = {
showApplication = false rank: "Guest"
} as User
character: any = {
class : "Warrior",
spec : "Arms"
}
ranks = _Rank ranks = _Rank
classes = _Class
selectedClass : Class = "Warrior"
selectedSpec = specs[this.selectedClass][0]
specs = specs[this.selectedClass]
showApplication = false
constructor( constructor(
private router : Router, private router : Router,
@@ -23,10 +38,32 @@ export class RegisterComponent implements OnInit{
if(loggedin){ if(loggedin){
this.router.navigateByUrl("/") this.router.navigateByUrl("/")
} }
}); })
} }
login(){ onSelectClass(){
this.loginApi.login(this.user.name, this.user.password) this.specs = specs[this.character.class]
setTimeout(() => {
this.character.spec = this.specs[0]
}, 25)
} }
onSelectSpec(){
}
async onSubmit(){
this.user.pwhash = await hash(this.user.pwhash)
const user = this.user
this.user = {} as User
const char = this.character
this.character = {}
try{
const usr = await this.loginApi.getUnprivilegedSocket().Authenticator.createUser(user)
}catch(e){
alert("Error creating user"+e)
}
}
} }
@@ -65,11 +65,8 @@ export class LoginApiService{
getCurrentUser = () : User | undefined => this.auth?this.auth.user:undefined getCurrentUser = () : User | undefined => this.auth?this.auth.user:undefined
login = async (username: string, password: string) : Promise<RPCSocket & SomeOf<FrontcraftFeatureIfc>> => { login = async (username: string, password: string) : Promise<RPCSocket & SomeOf<FrontcraftFeatureIfc>> => {
const buf = str2arraybuf(password) const pwHash = await hash(password)
const pwHash = await crypto.subtle.digest('SHA-256', buf); const auth = await this.socket.Authenticator.login(username, pwHash)
const auth = await this.socket.Authenticator.login(username, buf2hex(pwHash))
if(!auth){ if(!auth){
await this.logout() await this.logout()
@@ -129,6 +126,12 @@ function buf2hex(buffer) { // buffer is an ArrayBuffer
return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join(''); return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join('');
} }
export async function hash(value:string) : Promise<string>{
const buf = str2arraybuf(value)
const pwHash = await crypto.subtle.digest('SHA-256', buf);
return buf2hex(pwHash)
}
//angular depenency manager requires this //angular depenency manager requires this
export function initializeLoginSvc(svc: LoginApiService): () => Promise<any> { export function initializeLoginSvc(svc: LoginApiService): () => Promise<any> {
return svc.initialize return svc.initialize
+2 -2
View File
@@ -9,12 +9,12 @@
"moduleResolution": "node", "moduleResolution": "node",
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"target": "es2015", "target": "es5",
"typeRoots": [ "typeRoots": [
"node_modules/@types" "node_modules/@types"
], ],
"lib": [ "lib": [
"es2017", "es2019",
"dom" "dom"
], ],
"plugins": [ "plugins": [
+4 -3
View File
@@ -7,8 +7,9 @@
"declaration": true, "declaration": true,
"outDir": "./lib", "outDir": "./lib",
"strict": true, "strict": true,
"experimentalDecorators": true "experimentalDecorators": true,
"emitDecoratorMetadata": true
}, },
"include": ["src/backend/**/*"], "include": ["src/backend/**/*", "test/**/*"],
"exclude": ["node_modules", "**/__tests__/*"], "exclude": ["node_modules", "**/__tests__/*"]
} }