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": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
"integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true
}
}
@@ -1476,9 +1476,9 @@
"integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg="
},
"cyclist": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz",
"integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
"integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=",
"dev": true
},
"dashdash": {
@@ -5465,12 +5465,12 @@
"dev": true
},
"parallel-transform": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz",
"integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz",
"integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==",
"dev": true,
"requires": {
"cyclist": "~0.2.2",
"cyclist": "^1.0.1",
"inherits": "^2.0.3",
"readable-stream": "^2.1.5"
}
@@ -6130,9 +6130,9 @@
}
},
"rpclibrary": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/rpclibrary/-/rpclibrary-1.7.1.tgz",
"integrity": "sha512-Ibo3qfURnQgZAq0eA2o+L9+PWaloJ4PHZs4ak42LL9fRgdWZXn33HAtowV0K2HVckbvrBvFqj/+PWTrWWBSOeg==",
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/rpclibrary/-/rpclibrary-1.8.3.tgz",
"integrity": "sha512-8pQRbMXQKCf3+v+XM01d+1Bw73pg5YzgyhW/ACpYON1I4re5fZAjWGPyLc+ms+MlYQIlKc3Uk92PGot2sbb85Q==",
"requires": {
"bsock": "^0.1.9",
"http": "0.0.0",
@@ -6252,9 +6252,9 @@
}
},
"serialize-javascript": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.8.0.tgz",
"integrity": "sha512-3tHgtF4OzDmeKYj6V9nSyceRS0UJ3C7VqyD2Yj28vC/z2j6jG5FmFGahOKMD9CrglxTm3tETr87jEypaYV8DUg==",
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
"integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==",
"dev": true
},
"serve-static": {
@@ -6503,9 +6503,9 @@
}
},
"source-map-support": {
"version": "0.5.13",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
"integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
"version": "0.5.16",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz",
"integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==",
"dev": true,
"requires": {
"buffer-from": "^1.0.0",
@@ -6637,9 +6637,9 @@
}
},
"stream-shift": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
"integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
"integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
"dev": true
},
"streamroller": {
@@ -6799,9 +6799,9 @@
"dev": true
},
"terser": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.2.0.tgz",
"integrity": "sha512-6lPt7lZdZ/13icQJp8XasFOwZjFJkxFFIb/N1fhYEQNoNI3Ilo3KABZ9OocZvZoB39r6SiIk/0+v/bt8nZoSeA==",
"version": "4.6.4",
"resolved": "https://registry.npmjs.org/terser/-/terser-4.6.4.tgz",
"integrity": "sha512-5fqgBPLgVHZ/fVvqRhhUp9YUiGXhFJ9ZkrZWD9vQtFBR4QIGTnbsb+/kKqSqfgp3WnBwGWAFnedGTtmX1YTn0w==",
"dev": true,
"requires": {
"commander": "^2.20.0",
@@ -6810,16 +6810,16 @@
}
},
"terser-webpack-plugin": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz",
"integrity": "sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==",
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz",
"integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==",
"dev": true,
"requires": {
"cacache": "^12.0.2",
"find-cache-dir": "^2.1.0",
"is-wsl": "^1.1.0",
"schema-utils": "^1.0.0",
"serialize-javascript": "^1.7.0",
"serialize-javascript": "^2.1.2",
"source-map": "^0.6.1",
"terser": "^4.1.2",
"webpack-sources": "^1.4.0",
+1 -1
View File
@@ -45,7 +45,7 @@
"path": "^0.12.7",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.0",
"rpclibrary": "^1.7.1",
"rpclibrary": "^1.8.3",
"simple-git": "^1.124.0",
"spawn-sync": "^2.0.0",
"sqlite3": "^4.1.1",
+8 -6
View File
@@ -74,11 +74,14 @@ implements TableDefinitionExporter, IAdmin {
}
stop(){
Promise.race([
Promise.all( this.frontworkComponents.map(c => c.stop?c.stop():undefined )),
new Promise((res, rej) => { setTimeout(res, 250);})
]).catch(e => logger.warn(e))
.finally(() => { process.exit(0) })
Promise.all([
...this.frontworkComponents.map(c => c.stop?c.stop():undefined ),
])
.catch(e => logger.warn(e))
.finally(() => {
this.rpcServer.destroy()
process.exit(0)
})
}
protected configChangeHandler = (conf:AdminConf, key?:string) => {
@@ -202,7 +205,6 @@ implements TableDefinitionExporter, IAdmin {
return await this.knex.schema.createTable(def.name, def.tableBuilder)
})
)
return this.knex
}
}
+1 -1
View File
@@ -18,6 +18,6 @@ export type ItemManagerFeatureIfc = {
}
managePriorities: {
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
getUserRecordByToken: (tokenValue: string) => UserRecord | void
getUser: (username: string) => Promise<User | void>
adminLogout: (username: string) => Promise<void>
decrementCurrency: (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>
getCurrency: (user:User, tier:Tiers) => Promise<number>
changeRank: (user:User, rank: Rank) => Promise<User>
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']
getAuth: IUserManager['getAuth']
getUser: IUserManager['getUser']
changePassword: IUserManager['changePassword']
}
}
export type UserManagerFeatureIfc = {
manageUser: {
changeRank: IUserManager['changeRank']
adminLogout: IUserManager['adminLogout']
adminChangePassword: IUserManager['adminChangePassword']
}
modifyPermissions: {
setPermission: IUserManager['setPermission']
getPermissions: IUserManager['getPermissions']
changeRank: IUserManager['changeRank']
}
softreserveCurrency: {
+59 -26
View File
@@ -13,6 +13,10 @@ import { IUserManager } from "./Interface";
import { getLogger, Logger } from "log4js";
import { saltedHash } from "../../Util/hash";
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')
@@ -34,16 +38,16 @@ implements FrontworkComponent<UserManagerIfc, UserManagerFeatureIfc>, IUserManag
@Inject(IAdmin)
private admin: FrontworkAdmin
@Inject(GuildManager)
@Inject(IGuildManager)
private guild : GuildManager
@Inject(ItemManager)
@Inject(IItemManager)
private item : ItemManager
@Inject(RaidManager)
@Inject(IRaidManager)
private raid : RaidManager
@Inject(CharacterManager)
@Inject(ICharacterManager)
private character : CharacterManager
exporters :any[] = []
@@ -56,15 +60,23 @@ implements FrontworkComponent<UserManagerIfc, UserManagerFeatureIfc>, IUserManag
this.getAuth,
this.checkToken,
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',
exportRPCs: () => [
this.getPermissions,
this.setPermission,
this.changeRank
]
},{
name: 'softreserveCurrency' as 'softreserveCurrency',
@@ -84,6 +96,8 @@ implements FrontworkComponent<UserManagerIfc, UserManagerFeatureIfc>, IUserManag
rank: rank
})
await this.adminLogout(user.username)
return await this.admin
.knex('users')
.where({
@@ -132,8 +146,6 @@ implements FrontworkComponent<UserManagerIfc, UserManagerFeatureIfc>, IUserManag
})))
//start rankServers
getLogger('UserManager').debug('Starting rank servers')
let rankServers = { } as any
await Promise.all(_Rank.map(async (r,i) => {
const port = 20001 + i
@@ -145,25 +157,23 @@ implements FrontworkComponent<UserManagerIfc, UserManagerFeatureIfc>, IUserManag
}
}))
this.rankServers = rankServers
getLogger('UserManager').debug(Object.values(this.rankServers).length+" rank servers started")
setInterval(this.checkExpiredSessions, 600_000)
}
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
.values(this.rankServers)
.map(async state => {
Object.values(this.rankServers)
.map(state => {
try{
//return await state.server.destroy()
return state.server.destroy()
}catch(e){
getLogger('UserManager').warn(e)
}
})
);
}
checkExpiredSessions = () => {
@@ -198,6 +208,26 @@ implements FrontworkComponent<UserManagerIfc, UserManagerFeatureIfc>, IUserManag
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) => {
await this.admin.knex('rpcpermissions')
.where('rpcname', '=', permission.rpcname)
@@ -270,21 +300,27 @@ implements FrontworkComponent<UserManagerIfc, UserManagerFeatureIfc>, IUserManag
logout = async (username:string, tokenValue : string) : Promise<void> => {
try{
if(!this.checkTokenOwnedByUser(username, tokenValue)) return
return await this.adminLogout(username)
}catch(e){
}
}
adminLogout = async(username: string) : Promise<void> => {
try{
if(this.userLogins[username]){
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)
.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]
}
}catch(e){
console.log(e)
getLogger('UserManager').warn(e)
}
}
@@ -390,9 +426,6 @@ implements FrontworkComponent<UserManagerIfc, UserManagerFeatureIfc>, IUserManag
username = username.toLowerCase()
const maybeRecord = this.getUserRecordByToken(tokenValue)
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 true
+7 -6
View File
@@ -9,7 +9,7 @@ import { FrontworkComponent } from '../Types/FrontworkComponent';
export const Injector = new class {
injectionQueue :any[] = []
rootObj: any
rootInterface : Type<any>
root : Type<any>
rootModules: Type<any>[] = []
@@ -29,6 +29,7 @@ export const Injector = new class {
return this.moduleObjs[target.name] as any
if(target.name === this.rootInterface.name || target.name === this.root.name){
if(this.rootObj) return this.rootObj
let modules = this.modules.map(m => {
const module = new m.implementation()
if(m.implements)
@@ -36,10 +37,10 @@ export const Injector = new class {
this.moduleObjs[m.implementation.name] = module
return module
})
const rootobj = new this.root(modules);
const rootObj = new this.root(modules);
this.moduleObjs[this.rootInterface.name] = rootobj
this.moduleObjs[target.name] = rootobj
this.moduleObjs[this.rootInterface.name] = rootObj
this.moduleObjs[target.name] = rootObj
while(this.injectionQueue.length > 0){
const i = this.injectionQueue.shift()
if(this.moduleObjs[i.what.name])
@@ -47,8 +48,8 @@ export const Injector = new class {
else
this.injectionQueue.push(i)
}
return rootobj
this.rootObj = rootObj
return rootObj
}
this.moduleObjs[target.name] = new target()
return this.moduleObjs[target.name] as any
+2 -6
View File
@@ -9,12 +9,8 @@
"projectType": "application",
"architect": {
"build": {
"builder": "@angular-builders/custom-webpack:browser",
"builder": "@angular-devkit/build-angular:browser",
"options": {
"customWebpackConfig": {
"path": "./custom-webpack.config.js",
"replaceDuplicatePlugins": true
},
"preserveSymlinks": true,
"rebaseRootRelativeCssUrls": true,
"outputPath": "dist",
@@ -79,7 +75,7 @@
}
},
"serve": {
"builder": "@angular-builders/custom-webpack:dev-server",
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"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-toaster": "^7.0.0",
"bootstrap": "4.3.1",
"chart.js": "2.7.1",
"chart.js": "^2.9.3",
"ckeditor": "4.7.3",
"classlist.js": "1.1.20150312",
"core-js": "2.5.1",
@@ -65,11 +65,11 @@
"ng2-smart-table": "1.3.5",
"ngx-cookie-service": "^2.2.0",
"ngx-echarts": "^4.0.1",
"node-sass": "^4.12.0",
"node-sass": "^4.13.1",
"normalize.css": "6.0.0",
"pace-js": "1.0.2",
"roboto-fontface": "0.8.0",
"rpclibrary": "^1.7.1",
"rpclibrary": "^1.8.3",
"rxjs": "6.5.2",
"rxjs-compat": "6.3.0",
"socicon": "3.0.5",
@@ -80,11 +80,11 @@
"zone.js": "~0.9.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.800.2",
"@angular-devkit/build-angular": "~0.803.24",
"@angular/cli": "^8.0.2",
"@angular/compiler-cli": "^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",
"@types/d3-color": "1.0.5",
"@types/googlemaps": "^3.30.4",
@@ -1,9 +1,4 @@
<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">
<a (click)="toggleSidebar()" href="#" class="sidebar-toggle">
<nb-icon icon="menu-2-outline"></nb-icon>
@@ -35,12 +35,7 @@
[maxlength]="15"
[attr.aria-invalid]="password.invalid && password.touched ? true : null">
</div>
<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>
<br />
<button nbButton
fullWidth
status="primary"
@@ -35,28 +35,32 @@
[attr.aria-invalid]="password.invalid && password.touched ? true : null">
</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-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>
<br />
<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-option *ngFor="let race of races" [value]="race">{{race}}</nb-option>
</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-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-select>
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-select>
</span>
@@ -195,7 +195,7 @@ export class FrontcraftRaidComponent implements OnInit, OnDestroy{
this.displayedtokens = this.tokens
}else{
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 => {
return item.itemname.toLocaleLowerCase().includes(this.search.toLocaleLowerCase())
})
@@ -9,7 +9,7 @@ import { _Tiers, allItems, Tiers } from '../../../../../../backend/Types/Items';
@Component({
selector: 'buyToken',
template: `
<nb-card class="col-12 col-xl-9">
<nb-card style="width: 500px">
<nb-card-header>
Buy {{item.itemname}}
</nb-card-header>
@@ -19,8 +19,10 @@ import { _Tiers, allItems, Tiers } from '../../../../../../backend/Types/Items';
</p>
<div *ngIf="currency>0">
<nb-alert accent="danger" *ngIf="invalidatedTokens.length > 0">
Claiming this reserve invalidates the following streaks. <br />
This is not reversible.
Claiming this reserve puts the following streaks to zero <br />
<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">
<li>
[ {{item.level}} ]
@@ -6,12 +6,65 @@ accent="info">
<h2 style="text-transform: capitalize;">{{user.username}}</h2>
</nb-card-header>
<nb-card-body>
{{user.MC}} MC
{{user.BWL}} BWL
{{user.ZG}} ZG
{{user.AQ20}} AQ20
{{user.AQ40}} AQ40
<h3>Rank</h3>
<span *ngIf="!manageUser">
{{user.rank}}
</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
<br />
<ng-container *ngFor="let char of characters">
<character [name]="char.charactername" [link]="'character'"></character>
</ng-container>
@@ -1,9 +1,11 @@
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 { User, Spec, Character } from '../../../../../../backend/Types/Types';
import { getClassColor } from '../../../../../../backend/Types/PlayerSpecs';
import { _Tiers } from '../../../../../../backend/Types/Items';
import { _Rank } from '../../../../../../backend/Types/Types';
import { NbToastrService } from '@nebular/theme';
@Component({
selector: 'user-component',
@@ -11,22 +13,31 @@ import { _Tiers } from '../../../../../../backend/Types/Items';
})
export class FrontcraftUserComponent implements OnInit{
newPw
newPwConfirm
ranks = _Rank
myProfile = false
manageUser
user:User = {} as any
characters : (Character & Spec)[] = []
constructor(
private route: ActivatedRoute,
private api : ApiService
private api : ApiService,
private toastr: NbToastrService
){}
async ngOnInit(){
const param = this.route.snapshot.paramMap.get('name');
const usr = await this.api.get('UserManager').getUser(param)
if(!usr) return
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)
characters.forEach(async c => {
const tokens = await this.api.get('ItemManager').getTokens(c, _Tiers, true)
@@ -35,4 +46,24 @@ export class FrontcraftUserComponent implements OnInit{
})
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 { ShoutMessage } from '../../../../../backend/Components/Shoutbox/Interface';
import { saltedHash } from '../../../../../backend/Util/hash';
import { NbToastrService } from '@nebular/theme';
@Injectable()
export class ApiService{
@@ -16,7 +17,8 @@ export class ApiService{
constructor(
private injector: Injector,
private cookieSvc : CookieService,
private ngZone : NgZone
private ngZone : NgZone,
private toastr: NbToastrService
){}
getUnprivilegedSocket = () : RPCSocket & FrontcraftIfc => this.socket
@@ -31,29 +33,33 @@ export class ApiService{
window.location.hostname
)
const authSock = await sock.connect<RPCSocket & SomeOf<FrontcraftFeatureIfc>>(auth.token.value)
sock.hook('kick', () => {
console.log("I got kicked");
this.logout()
})
sock.hook('getUserData', () => auth)
sock.hook('navigate', (where:string) => {
this.ngZone.run( () => {
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.privSocket = authSock
this.cookieSvc.set('token', JSON.stringify(auth))
return authSock
}catch(e){
//login failed
throw new Error(e)
if(this.privSocket) this.privSocket.destroy()
return;
}
}
@@ -76,12 +82,25 @@ export class ApiService{
login = async (username: string, password: string) : Promise<RPCSocket & SomeOf<FrontcraftFeatureIfc>> => {
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){
await this.logout()
this.toastr.danger("Login failed", "Error")
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)
return sock
}
@@ -93,26 +112,50 @@ export class ApiService{
logout = async () => {
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()
this.privSocket = null
this.auth = null
this.ngZone.run(() => {
this.injector.get(Router).navigate(['/auth']);
})
}
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{
const cookie = JSON.parse(this.cookieSvc.get('token'))
if(cookie != null) {
try{
const auth = await sock.UserManager.getAuth(cookie.token.value)
if(!auth) return sock
const auth = await this.socket.UserManager.getAuth(cookie.token.value)
if(!auth) return this.socket
return await this.getPrivilegedSocket(auth)
}catch(e){
await this.logout()
+34 -11
View File
@@ -2,7 +2,35 @@
<html>
<head>
<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="/">
@@ -11,16 +39,11 @@
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<ngx-app>Loading...</ngx-app>
<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 id="nb-global-spinner" class="spinner">
<div class="blob blob-0"></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>
<ngx-app></ngx-app>
<div id="nb-global-spinner" class="spinner" style="background-color: #111; height: 100vh;">
<div class="loadTitle">
<h1 class="loadH">Tranquil</h1>
</div>
</div>
</body>
+3
View File
@@ -55,3 +55,6 @@ import 'core-js/es7/object';
if (typeof SVGElement.prototype.contains === 'undefined') {
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: 'manageRaid', ...adminsOnly
}, {
rpcname: 'manageUser', ...adminsOnly
}]
const testAccounts: protoAccount[] = [
@@ -271,12 +273,6 @@ describe('Frontcraft', () => {
undefined, 2, "str-to-ap bias"
),
makePrio(
'Band of Accuria',
{ class: 'Warrior', specname: 'Protection' },
undefined, 2, "hit bias"
),
makePrio(
'Bracers of Arcane Accuracy',
{ class: 'Warlock', specname: 'Demonology' },
@@ -376,22 +372,11 @@ describe('Frontcraft', () => {
undefined, 1, "hit bias"
),
makePrio(
'Chromatic Boots',
{ class: 'Warrior', specname: 'Protection' },
undefined, 2, "hit bias"
),
makePrio(
'Crul\'shorukh, Edge of Chaos',
undefined,
"Human", -2, "Non-human"
),
makePrio(
'Crul\'shorukh, Edge of Chaos',
{ class: 'Warrior', specname: 'Protection' },
undefined, 2, "nice dps bias"
),
makePrio(
'Crul\'shorukh, Edge of Chaos',
{ class: 'Warrior', specname: 'Fury' },