change pw, change rank UI, auto-refresh on disconnect, new loading screen

This commit is contained in:
peter
2020-02-25 21:31:39 +01:00
parent de76d37768
commit 96651913cd
23 changed files with 6442 additions and 2964 deletions
+29 -29
View File
@@ -1050,9 +1050,9 @@
} }
}, },
"yallist": { "yallist": {
"version": "3.0.3", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true "dev": true
} }
} }
@@ -1476,9 +1476,9 @@
"integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg=" "integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg="
}, },
"cyclist": { "cyclist": {
"version": "0.2.2", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
"integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=",
"dev": true "dev": true
}, },
"dashdash": { "dashdash": {
@@ -5465,12 +5465,12 @@
"dev": true "dev": true
}, },
"parallel-transform": { "parallel-transform": {
"version": "1.1.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz",
"integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==",
"dev": true, "dev": true,
"requires": { "requires": {
"cyclist": "~0.2.2", "cyclist": "^1.0.1",
"inherits": "^2.0.3", "inherits": "^2.0.3",
"readable-stream": "^2.1.5" "readable-stream": "^2.1.5"
} }
@@ -6130,9 +6130,9 @@
} }
}, },
"rpclibrary": { "rpclibrary": {
"version": "1.7.1", "version": "1.8.3",
"resolved": "https://registry.npmjs.org/rpclibrary/-/rpclibrary-1.7.1.tgz", "resolved": "https://registry.npmjs.org/rpclibrary/-/rpclibrary-1.8.3.tgz",
"integrity": "sha512-Ibo3qfURnQgZAq0eA2o+L9+PWaloJ4PHZs4ak42LL9fRgdWZXn33HAtowV0K2HVckbvrBvFqj/+PWTrWWBSOeg==", "integrity": "sha512-8pQRbMXQKCf3+v+XM01d+1Bw73pg5YzgyhW/ACpYON1I4re5fZAjWGPyLc+ms+MlYQIlKc3Uk92PGot2sbb85Q==",
"requires": { "requires": {
"bsock": "^0.1.9", "bsock": "^0.1.9",
"http": "0.0.0", "http": "0.0.0",
@@ -6252,9 +6252,9 @@
} }
}, },
"serialize-javascript": { "serialize-javascript": {
"version": "1.8.0", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.8.0.tgz", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
"integrity": "sha512-3tHgtF4OzDmeKYj6V9nSyceRS0UJ3C7VqyD2Yj28vC/z2j6jG5FmFGahOKMD9CrglxTm3tETr87jEypaYV8DUg==", "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==",
"dev": true "dev": true
}, },
"serve-static": { "serve-static": {
@@ -6503,9 +6503,9 @@
} }
}, },
"source-map-support": { "source-map-support": {
"version": "0.5.13", "version": "0.5.16",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz",
"integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"buffer-from": "^1.0.0", "buffer-from": "^1.0.0",
@@ -6637,9 +6637,9 @@
} }
}, },
"stream-shift": { "stream-shift": {
"version": "1.0.0", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
"integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
"dev": true "dev": true
}, },
"streamroller": { "streamroller": {
@@ -6799,9 +6799,9 @@
"dev": true "dev": true
}, },
"terser": { "terser": {
"version": "4.2.0", "version": "4.6.4",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.2.0.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.4.tgz",
"integrity": "sha512-6lPt7lZdZ/13icQJp8XasFOwZjFJkxFFIb/N1fhYEQNoNI3Ilo3KABZ9OocZvZoB39r6SiIk/0+v/bt8nZoSeA==", "integrity": "sha512-5fqgBPLgVHZ/fVvqRhhUp9YUiGXhFJ9ZkrZWD9vQtFBR4QIGTnbsb+/kKqSqfgp3WnBwGWAFnedGTtmX1YTn0w==",
"dev": true, "dev": true,
"requires": { "requires": {
"commander": "^2.20.0", "commander": "^2.20.0",
@@ -6810,16 +6810,16 @@
} }
}, },
"terser-webpack-plugin": { "terser-webpack-plugin": {
"version": "1.4.1", "version": "1.4.3",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz",
"integrity": "sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==", "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==",
"dev": true, "dev": true,
"requires": { "requires": {
"cacache": "^12.0.2", "cacache": "^12.0.2",
"find-cache-dir": "^2.1.0", "find-cache-dir": "^2.1.0",
"is-wsl": "^1.1.0", "is-wsl": "^1.1.0",
"schema-utils": "^1.0.0", "schema-utils": "^1.0.0",
"serialize-javascript": "^1.7.0", "serialize-javascript": "^2.1.2",
"source-map": "^0.6.1", "source-map": "^0.6.1",
"terser": "^4.1.2", "terser": "^4.1.2",
"webpack-sources": "^1.4.0", "webpack-sources": "^1.4.0",
+1 -1
View File
@@ -45,7 +45,7 @@
"path": "^0.12.7", "path": "^0.12.7",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rimraf": "^3.0.0", "rimraf": "^3.0.0",
"rpclibrary": "^1.7.1", "rpclibrary": "^1.8.3",
"simple-git": "^1.124.0", "simple-git": "^1.124.0",
"spawn-sync": "^2.0.0", "spawn-sync": "^2.0.0",
"sqlite3": "^4.1.1", "sqlite3": "^4.1.1",
+8 -6
View File
@@ -74,11 +74,14 @@ implements TableDefinitionExporter, IAdmin {
} }
stop(){ stop(){
Promise.race([ Promise.all([
Promise.all( this.frontworkComponents.map(c => c.stop?c.stop():undefined )), ...this.frontworkComponents.map(c => c.stop?c.stop():undefined ),
new Promise((res, rej) => { setTimeout(res, 250);}) ])
]).catch(e => logger.warn(e)) .catch(e => logger.warn(e))
.finally(() => { process.exit(0) }) .finally(() => {
this.rpcServer.destroy()
process.exit(0)
})
} }
protected configChangeHandler = (conf:AdminConf, key?:string) => { protected configChangeHandler = (conf:AdminConf, key?:string) => {
@@ -202,7 +205,6 @@ implements TableDefinitionExporter, IAdmin {
return await this.knex.schema.createTable(def.name, def.tableBuilder) return await this.knex.schema.createTable(def.name, def.tableBuilder)
}) })
) )
return this.knex return this.knex
} }
} }
+1 -1
View File
@@ -18,6 +18,6 @@ export type ItemManagerFeatureIfc = {
} }
managePriorities: { managePriorities: {
setPriority:IItemManager['setPriority'] setPriority:IItemManager['setPriority']
deletePriorits: IItemManager['deletePriority'] deletePriority: IItemManager['deletePriority']
} }
} }
+3 -1
View File
@@ -11,11 +11,13 @@ export class IUserManager{
checkToken: (token: string, rank: Rank) => boolean checkToken: (token: string, rank: Rank) => boolean
getUserRecordByToken: (tokenValue: string) => UserRecord | void getUserRecordByToken: (tokenValue: string) => UserRecord | void
getUser: (username: string) => Promise<User | void> getUser: (username: string) => Promise<User | void>
adminLogout: (username: string) => Promise<void>
decrementCurrency: (user: User, tier:Tiers, value: number) => Promise<void> decrementCurrency: (user: User, tier:Tiers, value: number) => Promise<void>
incrementCurrency: (user: User, tier:Tiers, value: number) => Promise<void> incrementCurrency: (user: User, tier:Tiers, value: number) => Promise<void>
setCurrency: (user: User, tier:Tiers, value: number) => Promise<void> setCurrency: (user: User, tier:Tiers, value: number) => Promise<void>
getCurrency: (user:User, tier:Tiers) => Promise<number> getCurrency: (user:User, tier:Tiers) => Promise<number>
changeRank: (user:User, rank: Rank) => Promise<User> changeRank: (user:User, rank: Rank) => Promise<User>
wipeCurrency: () => Promise<void> wipeCurrency: () => Promise<void>
changePassword: (userToken: string, pwHash: string) => Promise<User>
adminChangePassword: (user:User, pwHash: string) => Promise<User>
} }
+7 -1
View File
@@ -8,14 +8,20 @@ export type UserManagerIfc = {
createUser: IUserManager['createUser'] createUser: IUserManager['createUser']
getAuth: IUserManager['getAuth'] getAuth: IUserManager['getAuth']
getUser: IUserManager['getUser'] getUser: IUserManager['getUser']
changePassword: IUserManager['changePassword']
} }
} }
export type UserManagerFeatureIfc = { export type UserManagerFeatureIfc = {
manageUser: {
changeRank: IUserManager['changeRank']
adminLogout: IUserManager['adminLogout']
adminChangePassword: IUserManager['adminChangePassword']
}
modifyPermissions: { modifyPermissions: {
setPermission: IUserManager['setPermission'] setPermission: IUserManager['setPermission']
getPermissions: IUserManager['getPermissions'] getPermissions: IUserManager['getPermissions']
changeRank: IUserManager['changeRank']
} }
softreserveCurrency: { softreserveCurrency: {
+59 -26
View File
@@ -13,6 +13,10 @@ import { IUserManager } from "./Interface";
import { getLogger, Logger } from "log4js"; import { getLogger, Logger } from "log4js";
import { saltedHash } from "../../Util/hash"; import { saltedHash } from "../../Util/hash";
import { _Tiers, Tiers } from "../../Types/Items"; import { _Tiers, Tiers } from "../../Types/Items";
import { ICharacterManager } from "../Character/Interface";
import { IRaidManager } from "../Raid/Interface";
import { IItemManager } from "../Item/Interface";
import { IGuildManager } from "../Guild/Interface";
const uuid = require('uuid/v4') const uuid = require('uuid/v4')
@@ -34,16 +38,16 @@ implements FrontworkComponent<UserManagerIfc, UserManagerFeatureIfc>, IUserManag
@Inject(IAdmin) @Inject(IAdmin)
private admin: FrontworkAdmin private admin: FrontworkAdmin
@Inject(GuildManager) @Inject(IGuildManager)
private guild : GuildManager private guild : GuildManager
@Inject(ItemManager) @Inject(IItemManager)
private item : ItemManager private item : ItemManager
@Inject(RaidManager) @Inject(IRaidManager)
private raid : RaidManager private raid : RaidManager
@Inject(CharacterManager) @Inject(ICharacterManager)
private character : CharacterManager private character : CharacterManager
exporters :any[] = [] exporters :any[] = []
@@ -56,15 +60,23 @@ implements FrontworkComponent<UserManagerIfc, UserManagerFeatureIfc>, IUserManag
this.getAuth, this.getAuth,
this.checkToken, this.checkToken,
this.createUser, this.createUser,
this.getUser this.getUser,
this.changePassword
] ]
exportRPCFeatures = () => [{ exportRPCFeatures = () => [
{
name: 'manageUser' as 'manageUser',
exportRPCs: () => [
this.adminLogout,
this.changeRank,
this.adminChangePassword
]
},{
name: 'modifyPermissions' as 'modifyPermissions', name: 'modifyPermissions' as 'modifyPermissions',
exportRPCs: () => [ exportRPCs: () => [
this.getPermissions, this.getPermissions,
this.setPermission, this.setPermission,
this.changeRank
] ]
},{ },{
name: 'softreserveCurrency' as 'softreserveCurrency', name: 'softreserveCurrency' as 'softreserveCurrency',
@@ -84,6 +96,8 @@ implements FrontworkComponent<UserManagerIfc, UserManagerFeatureIfc>, IUserManag
rank: rank rank: rank
}) })
await this.adminLogout(user.username)
return await this.admin return await this.admin
.knex('users') .knex('users')
.where({ .where({
@@ -132,8 +146,6 @@ implements FrontworkComponent<UserManagerIfc, UserManagerFeatureIfc>, IUserManag
}))) })))
//start rankServers //start rankServers
getLogger('UserManager').debug('Starting rank servers')
let rankServers = { } as any let rankServers = { } as any
await Promise.all(_Rank.map(async (r,i) => { await Promise.all(_Rank.map(async (r,i) => {
const port = 20001 + i const port = 20001 + i
@@ -145,25 +157,23 @@ implements FrontworkComponent<UserManagerIfc, UserManagerFeatureIfc>, IUserManag
} }
})) }))
this.rankServers = rankServers this.rankServers = rankServers
getLogger('UserManager').debug(Object.values(this.rankServers).length+" rank servers started")
setInterval(this.checkExpiredSessions, 600_000) setInterval(this.checkExpiredSessions, 600_000)
} }
stop = async () => { stop = async () => {
Object.values(this.userLogins).forEach(x => Object.values(x.connections).forEach(c => c.destroy())) Object.values(this.userLogins).forEach(x => {
Object.values(x.connections).forEach(c => c.destroy())
})
await Promise.all(Object Object.values(this.rankServers)
.values(this.rankServers) .map(state => {
.map(async state => {
try{ try{
//return await state.server.destroy() return state.server.destroy()
}catch(e){ }catch(e){
getLogger('UserManager').warn(e) getLogger('UserManager').warn(e)
} }
}) })
);
} }
checkExpiredSessions = () => { checkExpiredSessions = () => {
@@ -198,6 +208,26 @@ implements FrontworkComponent<UserManagerIfc, UserManagerFeatureIfc>, IUserManag
return true return true
} }
changePassword = async (userToken:string, pwHash:string) : Promise<User> => {
const record = this.getUserRecordByToken(userToken)
if(!record) return {} as User
return await this.adminChangePassword(record.user, pwHash)
}
adminChangePassword = async(user:User, pwHash:string) : Promise<User> => {
const salted = await saltedHash(pwHash, salt)
await this.admin.knex('users')
.update({pwhash: salted})
.where({
username: user.username
})
const usr = await this.getUser(user.username)
if(!usr) return {} as any
await this.adminLogout(usr.username)
return usr
}
setPermission = async (permission: RPCPermission) => { setPermission = async (permission: RPCPermission) => {
await this.admin.knex('rpcpermissions') await this.admin.knex('rpcpermissions')
.where('rpcname', '=', permission.rpcname) .where('rpcname', '=', permission.rpcname)
@@ -270,21 +300,27 @@ implements FrontworkComponent<UserManagerIfc, UserManagerFeatureIfc>, IUserManag
logout = async (username:string, tokenValue : string) : Promise<void> => { logout = async (username:string, tokenValue : string) : Promise<void> => {
try{ try{
if(!this.checkTokenOwnedByUser(username, tokenValue)) return if(!this.checkTokenOwnedByUser(username, tokenValue)) return
return await this.adminLogout(username)
}catch(e){
}
}
adminLogout = async(username: string) : Promise<void> => {
try{
if(this.userLogins[username]){ if(this.userLogins[username]){
await Promise.all (Object.values(this.userLogins[username].connections).map(async (sock) => { await Promise.all (Object.values(this.userLogins[username].connections).map(async (sock) => {
await sock.call('navigate', '/auth/login') await sock.call('kick')
})) }))
}
Object.values(this.rankServers) Object.values(this.rankServers)
.forEach(state => { .forEach(state => {
state.allowed = state.allowed.filter(allowed => allowed !== tokenValue) state.allowed = state.allowed.filter(allowed => allowed !== this.userLogins[username].auth.token.value)
}) })
delete this.userLogins[username] delete this.userLogins[username]
}
}catch(e){ }catch(e){
console.log(e) getLogger('UserManager').warn(e)
} }
} }
@@ -390,9 +426,6 @@ implements FrontworkComponent<UserManagerIfc, UserManagerFeatureIfc>, IUserManag
username = username.toLowerCase() username = username.toLowerCase()
const maybeRecord = this.getUserRecordByToken(tokenValue) const maybeRecord = this.getUserRecordByToken(tokenValue)
if(!maybeRecord || maybeRecord.auth.user.username != username){ if(!maybeRecord || maybeRecord.auth.user.username != username){
getLogger('UserManager').warn(`Bad logout attempt
token by: ${maybeRecord?maybeRecord.auth.user.username:tokenValue}
tried to logout: ${username}`)
return false return false
} }
return true return true
+7 -6
View File
@@ -9,7 +9,7 @@ import { FrontworkComponent } from '../Types/FrontworkComponent';
export const Injector = new class { export const Injector = new class {
injectionQueue :any[] = [] injectionQueue :any[] = []
rootObj: any
rootInterface : Type<any> rootInterface : Type<any>
root : Type<any> root : Type<any>
rootModules: Type<any>[] = [] rootModules: Type<any>[] = []
@@ -29,6 +29,7 @@ export const Injector = new class {
return this.moduleObjs[target.name] as any return this.moduleObjs[target.name] as any
if(target.name === this.rootInterface.name || target.name === this.root.name){ if(target.name === this.rootInterface.name || target.name === this.root.name){
if(this.rootObj) return this.rootObj
let modules = this.modules.map(m => { let modules = this.modules.map(m => {
const module = new m.implementation() const module = new m.implementation()
if(m.implements) if(m.implements)
@@ -36,10 +37,10 @@ export const Injector = new class {
this.moduleObjs[m.implementation.name] = module this.moduleObjs[m.implementation.name] = module
return module return module
}) })
const rootobj = new this.root(modules); const rootObj = new this.root(modules);
this.moduleObjs[this.rootInterface.name] = rootobj this.moduleObjs[this.rootInterface.name] = rootObj
this.moduleObjs[target.name] = rootobj this.moduleObjs[target.name] = rootObj
while(this.injectionQueue.length > 0){ while(this.injectionQueue.length > 0){
const i = this.injectionQueue.shift() const i = this.injectionQueue.shift()
if(this.moduleObjs[i.what.name]) if(this.moduleObjs[i.what.name])
@@ -47,8 +48,8 @@ export const Injector = new class {
else else
this.injectionQueue.push(i) this.injectionQueue.push(i)
} }
this.rootObj = rootObj
return rootobj return rootObj
} }
this.moduleObjs[target.name] = new target() this.moduleObjs[target.name] = new target()
return this.moduleObjs[target.name] as any return this.moduleObjs[target.name] as any
+2 -6
View File
@@ -9,12 +9,8 @@
"projectType": "application", "projectType": "application",
"architect": { "architect": {
"build": { "build": {
"builder": "@angular-builders/custom-webpack:browser", "builder": "@angular-devkit/build-angular:browser",
"options": { "options": {
"customWebpackConfig": {
"path": "./custom-webpack.config.js",
"replaceDuplicatePlugins": true
},
"preserveSymlinks": true, "preserveSymlinks": true,
"rebaseRootRelativeCssUrls": true, "rebaseRootRelativeCssUrls": true,
"outputPath": "dist", "outputPath": "dist",
@@ -79,7 +75,7 @@
} }
}, },
"serve": { "serve": {
"builder": "@angular-builders/custom-webpack:dev-server", "builder": "@angular-devkit/build-angular:dev-server",
"options": { "options": {
"browserTarget": "ngx-admin-demo:build" "browserTarget": "ngx-admin-demo:build"
}, },
-4
View File
@@ -1,4 +0,0 @@
module.exports = {
externals: ['http', 'fs'],
node: {global: true, fs: 'empty'}
}
+6095 -2787
View File
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -50,7 +50,7 @@
"angular2-chartjs": "0.4.1", "angular2-chartjs": "0.4.1",
"angular2-toaster": "^7.0.0", "angular2-toaster": "^7.0.0",
"bootstrap": "4.3.1", "bootstrap": "4.3.1",
"chart.js": "2.7.1", "chart.js": "^2.9.3",
"ckeditor": "4.7.3", "ckeditor": "4.7.3",
"classlist.js": "1.1.20150312", "classlist.js": "1.1.20150312",
"core-js": "2.5.1", "core-js": "2.5.1",
@@ -65,11 +65,11 @@
"ng2-smart-table": "1.3.5", "ng2-smart-table": "1.3.5",
"ngx-cookie-service": "^2.2.0", "ngx-cookie-service": "^2.2.0",
"ngx-echarts": "^4.0.1", "ngx-echarts": "^4.0.1",
"node-sass": "^4.12.0", "node-sass": "^4.13.1",
"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.7.1", "rpclibrary": "^1.8.3",
"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",
@@ -80,11 +80,11 @@
"zone.js": "~0.9.1" "zone.js": "~0.9.1"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "~0.800.2", "@angular-devkit/build-angular": "~0.803.24",
"@angular/cli": "^8.0.2", "@angular/cli": "^8.0.2",
"@angular/compiler-cli": "^8.0.0", "@angular/compiler-cli": "^8.0.0",
"@angular/language-service": "8.0.0", "@angular/language-service": "8.0.0",
"@compodoc/compodoc": "1.0.1", "@compodoc/compodoc": "^1.1.11",
"@fortawesome/fontawesome-free": "^5.2.0", "@fortawesome/fontawesome-free": "^5.2.0",
"@types/d3-color": "1.0.5", "@types/d3-color": "1.0.5",
"@types/googlemaps": "^3.30.4", "@types/googlemaps": "^3.30.4",
@@ -1,9 +1,4 @@
<div class="header-container"> <div class="header-container">
<div>
<a (click)="toggleSidebar()" href="#" class="sidebar-toggle">
<nb-icon icon="menu-2-outline"></nb-icon>
</a>
</div>
<div class="logo-container"> <div class="logo-container">
<a (click)="toggleSidebar()" href="#" class="sidebar-toggle"> <a (click)="toggleSidebar()" href="#" class="sidebar-toggle">
<nb-icon icon="menu-2-outline"></nb-icon> <nb-icon icon="menu-2-outline"></nb-icon>
@@ -35,12 +35,7 @@
[maxlength]="15" [maxlength]="15"
[attr.aria-invalid]="password.invalid && password.touched ? true : null"> [attr.aria-invalid]="password.invalid && password.touched ? true : null">
</div> </div>
<br />
<div class="form-control-group accept-group">
<nb-checkbox name="rememberMe" [(ngModel)]="user.rememberMe" *ngIf="rememberMe">Remember me</nb-checkbox>
<a class="forgot-password" routerLink="../request-password">Forgot Password?</a>
</div>
<button nbButton <button nbButton
fullWidth fullWidth
status="primary" status="primary"
@@ -35,28 +35,32 @@
[attr.aria-invalid]="password.invalid && password.touched ? true : null"> [attr.aria-invalid]="password.invalid && password.touched ? true : null">
</div> </div>
<div class="form-control-group accept-group">
<nb-checkbox name="rememberMe" [(ngModel)]="user.rememberMe" *ngIf="rememberMe">Remember me</nb-checkbox>
</div>
<nb-card> <nb-card>
<nb-card-body> <nb-card-body>
<nb-checkbox [(ngModel)]="showApplication" name="amMember" #checkbox><!--I am already a member-->I understand this is super beta and things might not work or are not very pretty</nb-checkbox> <nb-checkbox [(ngModel)]="showApplication" name="amMember" #checkbox><!--I am already a member-->I understand this is super beta and things might not work or are not very pretty</nb-checkbox>
<br /> <br />
<span > <!--*ngIf="showApplication"--> <span > <!--*ngIf="showApplication"-->
And my main is &nbsp; <input name="preMember" [(ngModel)]="character.name" nbInput>, a And my main is &nbsp; <input nbInput [required]="true" name="preMember" [(ngModel)]="character.name" >, a
<nb-select [(selected)]="character.race" placeholder="Race" (selectedChange)="onSelectRace()"> <nb-select [(selected)]="character.race" placeholder="Race" (selectedChange)="onSelectRace()">
<nb-option *ngFor="let race of races" [value]="race">{{race}}</nb-option> <nb-option *ngFor="let race of races" [value]="race">{{race}}</nb-option>
</nb-select> </nb-select>
<nb-select [(selected)]="character.spec" placeholder="Spec" (selectedChange)="onSelectSpec()"> <nb-select
[(selected)]="character.spec"
placeholder="Spec"
(selectedChange)="onSelectSpec()">
<nb-option *ngFor="let spec of specs" [value]="spec">{{spec}}</nb-option> <nb-option *ngFor="let spec of specs" [value]="spec">{{spec}}</nb-option>
</nb-select> </nb-select>
<nb-select [(selected)]="character.class" placeholder="Class" (selectedChange)="onSelectClass()"> <nb-select
[(selected)]="character.class"
placeholder="Class"
(selectedChange)="onSelectClass()">
<nb-option *ngFor="let class of classes" [value]="class">{{class}}</nb-option> <nb-option *ngFor="let class of classes" [value]="class">{{class}}</nb-option>
</nb-select> </nb-select>
with rank with rank
<nb-select [(selected)]="user.rank" placeholder="Rank"> <nb-select
[(selected)]="user.rank"
placeholder="Rank">
<nb-option *ngFor="let rank of ranks" [value]="rank" >{{rank}}</nb-option> <nb-option *ngFor="let rank of ranks" [value]="rank" >{{rank}}</nb-option>
</nb-select> </nb-select>
</span> </span>
@@ -195,7 +195,7 @@ export class FrontcraftRaidComponent implements OnInit, OnDestroy{
this.displayedtokens = this.tokens this.displayedtokens = this.tokens
}else{ }else{
this.displayedtokens = {} this.displayedtokens = {}
Object.entries(this.tokens).forEach((e: [string, (Character & SRToken & Item)[]]) => { Object.entries(this.tokens).forEach((e: [string /*itemname*/, (Character & SRToken & Item)[]]) => {
const filteredTokens = e[1].filter(item => { const filteredTokens = e[1].filter(item => {
return item.itemname.toLocaleLowerCase().includes(this.search.toLocaleLowerCase()) return item.itemname.toLocaleLowerCase().includes(this.search.toLocaleLowerCase())
}) })
@@ -9,7 +9,7 @@ import { _Tiers, allItems, Tiers } from '../../../../../../backend/Types/Items';
@Component({ @Component({
selector: 'buyToken', selector: 'buyToken',
template: ` template: `
<nb-card class="col-12 col-xl-9"> <nb-card style="width: 500px">
<nb-card-header> <nb-card-header>
Buy {{item.itemname}} Buy {{item.itemname}}
</nb-card-header> </nb-card-header>
@@ -19,8 +19,10 @@ import { _Tiers, allItems, Tiers } from '../../../../../../backend/Types/Items';
</p> </p>
<div *ngIf="currency>0"> <div *ngIf="currency>0">
<nb-alert accent="danger" *ngIf="invalidatedTokens.length > 0"> <nb-alert accent="danger" *ngIf="invalidatedTokens.length > 0">
Claiming this reserve invalidates the following streaks. <br /> Claiming this reserve puts the following streaks to zero <br />
This is not reversible. <span>
<nb-icon icon="alert-triangle-outline" status="warning"></nb-icon>&nbsp;This is not reversible&nbsp;<nb-icon icon="alert-triangle-outline" status="warning"></nb-icon>
</span>
<ul *ngFor="let item of invalidatedTokens"> <ul *ngFor="let item of invalidatedTokens">
<li> <li>
[ {{item.level}} ] [ {{item.level}} ]
@@ -6,12 +6,65 @@ accent="info">
<h2 style="text-transform: capitalize;">{{user.username}}</h2> <h2 style="text-transform: capitalize;">{{user.username}}</h2>
</nb-card-header> </nb-card-header>
<nb-card-body> <nb-card-body>
{{user.MC}} MC
{{user.BWL}} BWL <h3>Rank</h3>
{{user.ZG}} ZG <span *ngIf="!manageUser">
{{user.AQ20}} AQ20 {{user.rank}}
{{user.AQ40}} AQ40 </span>
<nb-select
*ngIf="manageUser"
[(selected)]="user.rank"
placeholder="Rank"
(selectedChange)="onSelectRank()">
<nb-option *ngFor="let rank of ranks" [value]="rank" >{{rank}}</nb-option>
</nb-select>
<h3 *ngIf="myProfile || manageUser">Password</h3>
<form (ngSubmit)="submitPasswordChange()" #form="ngForm" aria-labelledby="title" *ngIf="myProfile || manageUser">
<input nbInput
fullWidth
type="password"
[(ngModel)]="newPw"
name="newPw"
id="input-newPw"
pattern=".+"
placeholder="New Password"
fieldSize="small"
autofocus
[required]="true">
<input nbInput
fullWidth
type="password"
[(ngModel)]="newPwConfirm"
name="newPwConfirm"
id="input-newPwConfirm"
pattern=".+"
placeholder="Confirm Password"
fieldSize="small"
autofocus
[required]="true">
<button nbButton
fullWidth
status="primary"
size="small"
[disabled]="!form.valid || newPw != newPwConfirm"
[class.btn-pulse]="submitted">
Change
</button>
</form>
<br />
<h3>Avaiable Reserves</h3>
{{user.MC}} MC |
{{user.BWL}} BWL |
{{user.ZG}} ZG |
{{user.AQ20}} AQ20 |
{{user.AQ40}} AQ40 |
{{user.Naxx}} Naxx {{user.Naxx}} Naxx
<br />
<ng-container *ngFor="let char of characters"> <ng-container *ngFor="let char of characters">
<character [name]="char.charactername" [link]="'character'"></character> <character [name]="char.charactername" [link]="'character'"></character>
</ng-container> </ng-container>
@@ -1,9 +1,11 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { ApiService } from '../../services/login-api'; import { ApiService, hash } from '../../services/login-api';
import { Router, ActivatedRoute } from '@angular/router'; import { Router, ActivatedRoute } from '@angular/router';
import { User, Spec, Character } from '../../../../../../backend/Types/Types'; import { User, Spec, Character } from '../../../../../../backend/Types/Types';
import { getClassColor } from '../../../../../../backend/Types/PlayerSpecs'; import { getClassColor } from '../../../../../../backend/Types/PlayerSpecs';
import { _Tiers } from '../../../../../../backend/Types/Items'; import { _Tiers } from '../../../../../../backend/Types/Items';
import { _Rank } from '../../../../../../backend/Types/Types';
import { NbToastrService } from '@nebular/theme';
@Component({ @Component({
selector: 'user-component', selector: 'user-component',
@@ -11,22 +13,31 @@ import { _Tiers } from '../../../../../../backend/Types/Items';
}) })
export class FrontcraftUserComponent implements OnInit{ export class FrontcraftUserComponent implements OnInit{
newPw
newPwConfirm
ranks = _Rank
myProfile = false
manageUser
user:User = {} as any user:User = {} as any
characters : (Character & Spec)[] = [] characters : (Character & Spec)[] = []
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
private api : ApiService private api : ApiService,
private toastr: NbToastrService
){} ){}
async ngOnInit(){ async ngOnInit(){
const param = this.route.snapshot.paramMap.get('name'); const param = this.route.snapshot.paramMap.get('name');
const usr = await this.api.get('UserManager').getUser(param) const usr = await this.api.get('UserManager').getUser(param)
if(!usr) return if(!usr) return
this.user = usr this.user = usr
this.manageUser = this.api.get('manageUser')
this.myProfile = this.api.getCurrentUser().username === this.user.username
const characters = await this.api.get('CharacterManager').getCharactersOfUser(this.user.username) const characters = await this.api.get('CharacterManager').getCharactersOfUser(this.user.username)
characters.forEach(async c => { characters.forEach(async c => {
const tokens = await this.api.get('ItemManager').getTokens(c, _Tiers, true) const tokens = await this.api.get('ItemManager').getTokens(c, _Tiers, true)
@@ -35,4 +46,24 @@ export class FrontcraftUserComponent implements OnInit{
}) })
this.characters = characters this.characters = characters
} }
async onSelectRank(){
const manage = this.api.get('manageUser')
if(!manage) return
await manage.changeRank(this.user, this.user.rank)
this.toastr.success("Rank changed to "+this.user.rank, "Update success")
}
async submitPasswordChange(){
const pw = await hash(this.newPw)
this.newPw = null
this.newPwConfirm = null
const manage = this.api.get('manageUser')
if(!manage){
await this.api.get('UserManager').changePassword(this.api.getAuth().token.value, pw)
}else{
await manage.adminChangePassword(this.user, pw)
}
}
} }
@@ -6,6 +6,7 @@ import { CookieService } from 'ngx-cookie-service';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { ShoutMessage } from '../../../../../backend/Components/Shoutbox/Interface'; import { ShoutMessage } from '../../../../../backend/Components/Shoutbox/Interface';
import { saltedHash } from '../../../../../backend/Util/hash'; import { saltedHash } from '../../../../../backend/Util/hash';
import { NbToastrService } from '@nebular/theme';
@Injectable() @Injectable()
export class ApiService{ export class ApiService{
@@ -16,7 +17,8 @@ export class ApiService{
constructor( constructor(
private injector: Injector, private injector: Injector,
private cookieSvc : CookieService, private cookieSvc : CookieService,
private ngZone : NgZone private ngZone : NgZone,
private toastr: NbToastrService
){} ){}
getUnprivilegedSocket = () : RPCSocket & FrontcraftIfc => this.socket getUnprivilegedSocket = () : RPCSocket & FrontcraftIfc => this.socket
@@ -31,29 +33,33 @@ export class ApiService{
window.location.hostname window.location.hostname
) )
const authSock = await sock.connect<RPCSocket & SomeOf<FrontcraftFeatureIfc>>(auth.token.value)
sock.hook('kick', () => { sock.hook('kick', () => {
console.log("I got kicked"); this.logout()
}) })
sock.hook('getUserData', () => auth) sock.hook('getUserData', () => auth)
sock.hook('navigate', (where:string) => { sock.hook('navigate', (where:string) => {
this.ngZone.run( () => { this.ngZone.run( () => {
this.injector.get(Router).navigateByUrl(where) this.injector.get(Router).navigateByUrl(where)
}) })
}) })
sock.on('error', (e) => {
sock.destroy();
sock.on('close', async () => {
//handled via unprivileged socket
}) })
sock.on('error', async (e) => {
//handled via unprivileged socket
})
const authSock = await sock.connect<RPCSocket & SomeOf<FrontcraftFeatureIfc>>(auth.token.value)
this.auth = auth this.auth = auth
this.privSocket = authSock this.privSocket = authSock
this.cookieSvc.set('token', JSON.stringify(auth)) this.cookieSvc.set('token', JSON.stringify(auth))
return authSock return authSock
}catch(e){ }catch(e){
//login failed if(this.privSocket) this.privSocket.destroy()
throw new Error(e) return;
} }
} }
@@ -76,12 +82,25 @@ export class ApiService{
login = async (username: string, password: string) : Promise<RPCSocket & SomeOf<FrontcraftFeatureIfc>> => { login = async (username: string, password: string) : Promise<RPCSocket & SomeOf<FrontcraftFeatureIfc>> => {
const pwHash = await hash(password) const pwHash = await hash(password)
const auth = await this.socket.UserManager.login(username, pwHash) let auth
try{
auth = await this.socket.UserManager.login(username, pwHash)
}catch(e){
this.toastr.danger("Login failed", "Error")
return
}
if(!auth){ if(!auth){
await this.logout() await this.logout()
this.toastr.danger("Login failed", "Error")
throw new Error("Login failed") throw new Error("Login failed")
} }
//attach error handler now so failed logins dont trigger reloads
this.socket.on('error', async (err) => {
location.reload()
})
const sock = await this.getPrivilegedSocket(auth) const sock = await this.getPrivilegedSocket(auth)
return sock return sock
} }
@@ -93,26 +112,50 @@ export class ApiService{
logout = async () => { logout = async () => {
this.cookieSvc.set('token', undefined) this.cookieSvc.set('token', undefined)
if(this.auth) await this.socket.UserManager.logout(this.auth.user.username, this.auth.token.value) if(this.auth){
try{
await this.socket.UserManager.logout(this.auth.user.username, this.auth.token.value)
}catch(e){
//socket is dead
}
}
if(this.privSocket) this.privSocket.destroy() if(this.privSocket) this.privSocket.destroy()
this.privSocket = null this.privSocket = null
this.auth = null this.auth = null
this.ngZone.run(() => { this.ngZone.run(() => {
this.injector.get(Router).navigate(['/auth']); this.injector.get(Router).navigate(['/auth']);
}) })
} }
initialize = async () : Promise<any> => { initialize = async () : Promise<any> => {
const sock = await RPCSocket.makeSocket<FrontcraftIfc>(20000, window.location.hostname) if(this.socket){
this.socket.destroy()
this.socket = null
}
try{
let conn = new RPCSocket(20000, window.location.hostname)
conn.on('close', async () => {
})
this.socket = await conn.connect<FrontcraftIfc>()
}catch(e){
alert('Unable to connect. The server appears to be down.\nPress OK to refresh the page')
location.reload()
return;
}
this.socket = sock
try{ try{
const cookie = JSON.parse(this.cookieSvc.get('token')) const cookie = JSON.parse(this.cookieSvc.get('token'))
if(cookie != null) { if(cookie != null) {
try{ try{
const auth = await sock.UserManager.getAuth(cookie.token.value) const auth = await this.socket.UserManager.getAuth(cookie.token.value)
if(!auth) return sock if(!auth) return this.socket
return await this.getPrivilegedSocket(auth) return await this.getPrivilegedSocket(auth)
}catch(e){ }catch(e){
await this.logout() await this.logout()
+34 -11
View File
@@ -2,7 +2,35 @@
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>ngx-admin Demo Application</title> <title>Frontcraft</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Montserrat">
<style>
@import url('https://fonts.googleapis.com/css?family=Montserrat');
.loadTitle {
font-family: 'Montserrat';
text-align: center;
color: #FFF;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
letter-spacing: 1px;
padding-top: calc(50vh - 120px);
}
.loadH {
background-image: url('https://media.giphy.com/media/26BROrSHlmyzzHf3i/giphy.gif');
background-size: cover;
color: transparent;
background-clip: text;
-moz-background-clip: text;
-webkit-background-clip: text;
text-transform: uppercase;
font-size: min(10vw, 120px);
margin: 10px 0;
}
</style>
<base href="/"> <base href="/">
@@ -11,16 +39,11 @@
<link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="icon" type="image/x-icon" href="favicon.ico">
</head> </head>
<body> <body>
<ngx-app>Loading...</ngx-app> <ngx-app></ngx-app>
<div id="nb-global-spinner" class="spinner" style="background-color: #111; height: 100vh;">
<style>@-webkit-keyframes spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@-moz-keyframes spin{0%{-moz-transform:rotate(0)}100%{-moz-transform:rotate(360deg)}}@keyframes spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}.spinner{position:fixed;top:0;left:0;width:100%;height:100%;z-index:1003;background: #000000;overflow:hidden} .spinner div:first-child{display:block;position:relative;left:50%;top:50%;width:150px;height:150px;margin:-75px 0 0 -75px;border-radius:50%;box-shadow:0 3px 3px 0 rgba(255,56,106,1);transform:translate3d(0,0,0);animation:spin 2s linear infinite} .spinner div:first-child:after,.spinner div:first-child:before{content:'';position:absolute;border-radius:50%} .spinner div:first-child:before{top:5px;left:5px;right:5px;bottom:5px;box-shadow:0 3px 3px 0 rgb(255, 228, 32);-webkit-animation:spin 3s linear infinite;animation:spin 3s linear infinite} .spinner div:first-child:after{top:15px;left:15px;right:15px;bottom:15px;box-shadow:0 3px 3px 0 rgba(61, 175, 255,1);animation:spin 1.5s linear infinite}</style> <div class="loadTitle">
<div id="nb-global-spinner" class="spinner"> <h1 class="loadH">Tranquil</h1>
<div class="blob blob-0"></div> </div>
<div class="blob blob-1"></div>
<div class="blob blob-2"></div>
<div class="blob blob-3"></div>
<div class="blob blob-4"></div>
<div class="blob blob-5"></div>
</div> </div>
</body> </body>
+3
View File
@@ -55,3 +55,6 @@ import 'core-js/es7/object';
if (typeof SVGElement.prototype.contains === 'undefined') { if (typeof SVGElement.prototype.contains === 'undefined') {
SVGElement.prototype.contains = HTMLDivElement.prototype.contains; SVGElement.prototype.contains = HTMLDivElement.prototype.contains;
} }
(window as any).global = window;
(window as any).global.Buffer = (window as any).global.Buffer || require('buffer').Buffer;
+2 -17
View File
@@ -53,6 +53,8 @@ const defaultPermissions = [
rpcname: 'softreserveCurrency', ...adminsOnly rpcname: 'softreserveCurrency', ...adminsOnly
}, { }, {
rpcname: 'manageRaid', ...adminsOnly rpcname: 'manageRaid', ...adminsOnly
}, {
rpcname: 'manageUser', ...adminsOnly
}] }]
const testAccounts: protoAccount[] = [ const testAccounts: protoAccount[] = [
@@ -271,12 +273,6 @@ describe('Frontcraft', () => {
undefined, 2, "str-to-ap bias" undefined, 2, "str-to-ap bias"
), ),
makePrio(
'Band of Accuria',
{ class: 'Warrior', specname: 'Protection' },
undefined, 2, "hit bias"
),
makePrio( makePrio(
'Bracers of Arcane Accuracy', 'Bracers of Arcane Accuracy',
{ class: 'Warlock', specname: 'Demonology' }, { class: 'Warlock', specname: 'Demonology' },
@@ -376,22 +372,11 @@ describe('Frontcraft', () => {
undefined, 1, "hit bias" undefined, 1, "hit bias"
), ),
makePrio(
'Chromatic Boots',
{ class: 'Warrior', specname: 'Protection' },
undefined, 2, "hit bias"
),
makePrio( makePrio(
'Crul\'shorukh, Edge of Chaos', 'Crul\'shorukh, Edge of Chaos',
undefined, undefined,
"Human", -2, "Non-human" "Human", -2, "Non-human"
), ),
makePrio(
'Crul\'shorukh, Edge of Chaos',
{ class: 'Warrior', specname: 'Protection' },
undefined, 2, "nice dps bias"
),
makePrio( makePrio(
'Crul\'shorukh, Edge of Chaos', 'Crul\'shorukh, Edge of Chaos',
{ class: 'Warrior', specname: 'Fury' }, { class: 'Warrior', specname: 'Fury' },