浏览代码

rework login and make register

master
peter 5 年前
父节点
当前提交
030908376b
共有 39 个文件被更改,包括 687 次插入418 次删除
  1. 3
    3
      package-lock.json
  2. 1
    1
      package.json
  3. 2
    1
      src/backend/Admin/Admin.ts
  4. 3
    5
      src/backend/Components/Eventbus.ts
  5. 1
    1
      src/backend/Components/Guild/GuildManager.ts
  6. 2
    2
      src/backend/Components/Item/ItemManager.ts
  7. 6
    6
      src/backend/Components/PluginLoader.ts
  8. 6
    6
      src/backend/Components/RPCConfigLoader.ts
  9. 6
    6
      src/backend/Components/Raid/RaidManager.ts
  10. 291
    0
      src/backend/Components/User/LoginManager.ts
  11. 7
    5
      src/backend/Components/User/RPCInterface.ts
  12. 0
    227
      src/backend/Components/User/UserManager.ts
  13. 3
    3
      src/backend/Launcher.ts
  14. 7
    8
      src/backend/Types/Types.ts
  15. 6
    6
      src/frontend/package-lock.json
  16. 1
    1
      src/frontend/package.json
  17. 2
    2
      src/frontend/src/app/@theme/components/footer/footer.component.ts
  18. 1
    4
      src/frontend/src/app/@theme/components/header/header.component.html
  19. 7
    3
      src/frontend/src/app/@theme/components/header/header.component.ts
  20. 1
    0
      src/frontend/src/app/@theme/layouts/index.ts
  21. 9
    0
      src/frontend/src/app/@theme/layouts/one-column-no-sidebar/one-column.layout.scss
  22. 19
    0
      src/frontend/src/app/@theme/layouts/one-column-no-sidebar/one-column.layout.ts
  23. 2
    0
      src/frontend/src/app/@theme/theme.module.ts
  24. 0
    5
      src/frontend/src/app/app-routing.module.ts
  25. 4
    2
      src/frontend/src/app/app.module.ts
  26. 1
    2
      src/frontend/src/app/demo_pages/pages-menu.ts
  27. 1
    0
      src/frontend/src/app/demo_pages/pages-routing.module.ts
  28. 15
    6
      src/frontend/src/app/frontcraft/auth/auth-layout.component.ts
  29. 10
    0
      src/frontend/src/app/frontcraft/auth/auth-routing.module.ts
  30. 9
    2
      src/frontend/src/app/frontcraft/auth/auth.module.ts
  31. 9
    44
      src/frontend/src/app/frontcraft/auth/login/login.component.html
  32. 8
    1
      src/frontend/src/app/frontcraft/auth/login/login.component.ts
  33. 1
    0
      src/frontend/src/app/frontcraft/auth/logout/logout.component.html
  34. 19
    0
      src/frontend/src/app/frontcraft/auth/logout/logout.component.ts
  35. 89
    0
      src/frontend/src/app/frontcraft/auth/register/register.component.html
  36. 32
    0
      src/frontend/src/app/frontcraft/auth/register/register.component.ts
  37. 25
    10
      src/frontend/src/app/frontcraft/pages/pages-layout.component.ts
  38. 4
    2
      src/frontend/src/app/frontcraft/pages/pages.module.ts
  39. 74
    54
      src/frontend/src/app/frontcraft/services/login-api.ts

+ 3
- 3
package-lock.json 查看文件

@@ -4961,9 +4961,9 @@
4961 4961
       }
4962 4962
     },
4963 4963
     "rpclibrary": {
4964
-      "version": "1.4.1",
4965
-      "resolved": "https://registry.npmjs.org/rpclibrary/-/rpclibrary-1.4.1.tgz",
4966
-      "integrity": "sha512-Tv9zOBVT8JFMZfTJgmnX9vvUvtALCDtj/cnlYw2SxsFnj1MuH9VgPniSfThUgdwZZknjFuYVfhLaYb9Yl+eRxQ==",
4964
+      "version": "1.5.2",
4965
+      "resolved": "https://registry.npmjs.org/rpclibrary/-/rpclibrary-1.5.2.tgz",
4966
+      "integrity": "sha512-BI08DxIcndBf25Rd1k957cTfxhNO9Mr1Y3YBSEuYG2dzI5x2/+T83N3swTn68esjLlzvbcmqMHncd5sCsVmN8w==",
4967 4967
       "requires": {
4968 4968
         "bsock": "^0.1.9",
4969 4969
         "http": "0.0.0",

+ 1
- 1
package.json 查看文件

@@ -37,7 +37,7 @@
37 37
     "node-fetch": "^2.6.0",
38 38
     "path": "^0.12.7",
39 39
     "rimraf": "^3.0.0",
40
-    "rpclibrary": "^1.4.1",
40
+    "rpclibrary": "^1.5.2",
41 41
     "simple-git": "^1.124.0",
42 42
     "spawn-sync": "^2.0.0",
43 43
     "sqlite3": "^4.1.0",

+ 2
- 1
src/backend/Admin/Admin.ts 查看文件

@@ -18,6 +18,7 @@ export class FrontworkAdmin
18 18
 implements TableDefinitionExporter {
19 19
     knex:Knex
20 20
     config: RPCConfigLoader<AdminConf>
21
+    rpcServer: RPCServer
21 22
 
22 23
     private express
23 24
     private httpServer
@@ -75,7 +76,7 @@ implements TableDefinitionExporter {
75 76
     }
76 77
 
77 78
     private startWebsocket(){
78
-        new RPCServer(20000, [
79
+        this.rpcServer = new RPCServer(20000, [
79 80
             ...this.components,
80 81
         ])
81 82
     }

+ 3
- 5
src/backend/Components/Eventbus.ts 查看文件

@@ -73,15 +73,13 @@ implements RPCExporter<EventbusIfc, "Eventbus">, TableDefinitionExporter {
73 73
     exportRPCs(){
74 74
         return [{
75 75
             name: 'getNotificationLog' as 'getNotificationLog',
76
-            call: async () => await this.getNotificationLog()
76
+            call: this.getNotificationLog
77 77
         },{
78 78
             name: 'pushNotification' as 'pushNotification',
79
-            call: async (notification: Notification) => { return await this.pushNotification(notification) }
79
+            call: this.pushNotification
80 80
         },{
81 81
             name: 'subscribeNotificaitons' as 'subscribeNotifications',
82
-            hook: async (callback: Function) => {
83
-                return await this.subscribeNotifications(callback)
84
-            }
82
+            hook: async (callback: Function) =>  await this.subscribeNotifications(callback)
85 83
         }]
86 84
     }
87 85
 

+ 1
- 1
src/backend/Components/Guild/GuildManager.ts 查看文件

@@ -33,7 +33,7 @@ implements FrontworkComponent<GuildManagerIfc, GuildManagerFeatureIfc>{
33 33
 
34 34
     exportRPCs = () => [{
35 35
         name: 'getHeadCount' as 'getHeadCount',
36
-        call: async () => await this.headCount()
36
+        call: this.headCount
37 37
     },{
38 38
         name: 'getGuildInfo' as 'getGuildInfo',
39 39
         call: async () => this.guild.getConfig()

+ 2
- 2
src/backend/Components/Item/ItemManager.ts 查看文件

@@ -29,10 +29,10 @@ implements FrontworkComponent<ItemManagerIfc, ItemManagerFeatureIfc>, TableDefin
29 29
     exportRPCs(): RPC<any, any>[]{
30 30
         return [{
31 31
             name: 'getItems',
32
-            call: async () => await this.getItems()
32
+            call: this.getItems
33 33
         },{
34 34
             name: 'getItem',
35
-            call: async (name:string) => await this.getItem(name) 
35
+            call: this.getItem
36 36
         }]
37 37
     }
38 38
 

+ 6
- 6
src/backend/Components/PluginLoader.ts 查看文件

@@ -146,22 +146,22 @@ implements RPCExporter<PluginLoaderIfc, "PluginLoader">{
146 146
     exportRPCs(){
147 147
         return [{
148 148
             name: "installPlugin" as "installPlugin",
149
-            call: async (name:string, force = false) => {return await this.installPlugin(name, force)},
149
+            call: this.installPlugin,
150 150
         },{
151 151
             name: "startPlugin" as "startPlugin",
152
-            call: async (name:string) => {return await this.startPlugin(name)},
152
+            call: this.startPlugin,
153 153
         },{
154 154
             name: "updatePlugin" as "updatePlugin",
155
-            call: async (name:string) => {return await this.updatePlugin(name)},
155
+            call: this.updatePlugin,
156 156
         },{
157 157
             name: "setPluginVersion" as "setPluginVersion",
158
-            call: async (name:string, tag:string) => {return await this.setPluginVersion(name, tag)},
158
+            call: this.setPluginVersion,
159 159
         },{
160 160
             name: "getLoadedPluginNames" as "getLoadedPluginNames",
161
-            call: async () => {return this.getPlugins().map(p => p.name)},
161
+            call: async () => this.getPlugins().map(p => p.name),
162 162
         },{
163 163
             name: "selfUpdate" as "selfUpdate",
164
-            call: async (force: boolean) => {return await this.selfUpdate(force)},
164
+            call: this.selfUpdate,
165 165
         }]
166 166
     }
167 167
 }

+ 6
- 6
src/backend/Components/RPCConfigLoader.ts 查看文件

@@ -21,22 +21,22 @@ implements RPCExporter<ConfigLoaderIfc<ConfT>, "Config">{
21 21
     exportRPCs() {
22 22
         return [{
23 23
             name: "getConfig" as "getConfig",
24
-            call: () => { return this.getConfig() }
24
+            call: this.getConfig
25 25
         },{
26 26
             name: "resetConfig" as "resetConfig",
27
-            call: () => { return this.resetConfig() }
27
+            call: this.resetConfig
28 28
         },{
29 29
             name: "setConfig" as "setConfig",
30
-            call: (conf:ConfT) => { return this.setConfig(conf) }
30
+            call: this.setConfig
31 31
         },{
32 32
             name: "setConfigKey" as "setConfigKey",
33
-            call: (key:string, value:any) => { return this.setConfigKey(key, value) }
33
+            call: this.setConfigKey
34 34
         },{
35 35
             name: "deleteConfigKey" as "deleteConfigKey",
36
-            call: (key:string) => { return this.deleteConfigKey(key) }
36
+            call: this.deleteConfigKey
37 37
         },{
38 38
             name: "getConfigKey" as "getConfigKey",
39
-            call: (key:string) => { return this.getConfigKey(key) }
39
+            call: this.getConfigKey
40 40
         }]
41 41
     }
42 42
 }

+ 6
- 6
src/backend/Components/Raid/RaidManager.ts 查看文件

@@ -15,25 +15,25 @@ implements FrontworkComponent<RaidManagerIfc, RaidManagerFeatureIfc>{
15 15
             name: 'manageRaid' as 'manageRaid',
16 16
             exportRPCs: () => [{
17 17
                 name: 'createRaid' as 'createRaid',
18
-                call: async (raid:Raid) => await this.createRaid(raid)
18
+                call: this.createRaid
19 19
             },{
20 20
                 name: 'addSignup' as 'addSignup',
21
-                call: async(signup: Signup) => await this.addSignup(signup)
21
+                call: this.addSignup
22 22
             },{
23 23
                 name: 'removeSignup' as 'removeSignup',
24
-                call: async(signup: Signup) => await this.removeSignup(signup)
24
+                call: this.removeSignup
25 25
             }]
26 26
         },{
27 27
             name: 'signup' as 'signup',
28 28
             exportRPCs: () => [{
29 29
                 name: 'getRaids' as 'getRaids',
30
-                call: async () => await this.getRaids()
30
+                call: this.getRaids
31 31
             },{
32 32
                 name: 'getSingups' as 'getSingups',
33
-                call: async(raid: Raid) => await this.getSignups(raid)
33
+                call: this.getSignups
34 34
             },{
35 35
                 name: 'sign' as 'sign',
36
-                call: async(user:User, raid:Raid) => await this.sign(user, raid)
36
+                call: this.sign
37 37
             }]
38 38
         },]
39 39
     }

+ 291
- 0
src/backend/Components/User/LoginManager.ts 查看文件

@@ -0,0 +1,291 @@
1
+import { RPCServer, Socket } from "rpclibrary";
2
+import { TableDefiniton, AnyRPCExporter, User, RPCPermission, Token, Auth, Rank, FrontcraftFeatureIfc, _Rank } from "../../Types/Types";
3
+import { FrontworkAdmin } from "../../Admin/Admin";
4
+import { PrivilegedRPCExporter } from "../../Types/PrivilegedRPCExporter";
5
+import { FrontworkComponent } from "../../Types/FrontworkComponent";
6
+import { getSpecTableData } from "../../Types/PlayerSpecs"
7
+import { LoginManagerIfc, LoginManagerFeatureIfc } from "./RPCInterface";
8
+const uuid = require('uuid/v4')
9
+
10
+const ONE_WEEK = 604800000 
11
+
12
+type Serverstate = {
13
+    server: RPCServer,
14
+    port : number,
15
+    allowed: string[]
16
+}
17
+
18
+export class LoginManager
19
+implements FrontworkComponent<LoginManagerIfc, LoginManagerFeatureIfc>{
20
+    name = "Authenticator" as "Authenticator";   
21
+    admin:FrontworkAdmin
22
+    
23
+    rankServers : {[rank in Rank] : Serverstate}
24
+    userLogins : {[username in string] : {
25
+        connections: {[port in number]: Socket}
26
+        auth: Auth
27
+    }} = {}
28
+
29
+    constructor(
30
+        private exporters: PrivilegedRPCExporter[]
31
+    ){}
32
+
33
+    exportRPCs = () => [
34
+        {
35
+            name: 'login' as 'login',
36
+            call: this.login 
37
+        },{
38
+            name: 'logout' as 'logout',
39
+            call: this.logout
40
+        },{
41
+            name: 'getAuth' as 'getAuth',
42
+            call: this.getAuth
43
+        },{
44
+            name: 'checkToken' as 'checkToken',
45
+            call: async (tokenValue : string, rank: Rank) => this.checkToken(tokenValue, rank)
46
+        }
47
+    ]
48
+
49
+    onSetAdmin(admin:FrontworkAdmin){
50
+        this.exporters.forEach(e => e['admin'] = admin)
51
+    }
52
+
53
+    exportRPCFeatures = () => [
54
+        {
55
+            name: 'createUser' as 'createUser',
56
+            exportRPCs: () => [{
57
+                name: 'createUser' as 'createUser',
58
+                call: this.createUser
59
+            }]
60
+        },{
61
+            name: 'modifyPermissions' as 'modifyPermissions',
62
+            exportRPCs: () => [{
63
+                name: 'getPermissions' as 'getPermissions',
64
+                call: this.getPermissions 
65
+            },{
66
+                name: 'setPermission' as 'setPermission',
67
+                call: this.setPermission
68
+            }]
69
+        }
70
+    ]
71
+    
72
+    
73
+    getTableDefinitions = (): TableDefiniton[] => [
74
+        {
75
+            name: 'users',
76
+            tableBuilder: (table) => {
77
+                table.increments("id").primary()
78
+                table.string("name").notNullable().unique()
79
+                table.string("pwhash").notNullable()
80
+                table.string("rank").notNullable()
81
+                table.string("email").nullable().unique()
82
+            }
83
+        },{
84
+            name: 'characters',
85
+            tableBuilder: (table) => {
86
+                table.increments("id").primary()
87
+                table.string("name").notNullable().unique()
88
+                table.integer("specid").notNullable()
89
+                table.foreign("specid").references("specs.id")
90
+                table.integer("userid").notNullable()
91
+                table.foreign("userid").references("users.id")
92
+            }
93
+        },{
94
+            name: 'rpcpermissions',
95
+            tableBuilder: (table) => {
96
+                table.string("name").primary().notNullable()
97
+                table.boolean("ADMIN").defaultTo(true).notNullable()
98
+                _Rank.forEach(r => table.boolean(r).defaultTo(false).notNullable())
99
+            }
100
+        },{
101
+            name: 'specs',
102
+            tableBuilder: (table) => {
103
+                table.increments("id")
104
+                table.string('class')
105
+                table.string('name')
106
+            }
107
+        }
108
+        ,...this.exporters.flatMap(exp => exp['getTableDefinitions']?exp['getTableDefinitions']():undefined)
109
+    ]
110
+
111
+    initialize = async () => {
112
+        //set up permissions
113
+        await Promise.all( 
114
+            [this, ...this.exporters].flatMap(exp => exp.exportRPCFeatures().map(async (feature) => {
115
+            try{
116
+                await this.admin.knex.insert({ name: feature.name }).into('rpcpermissions')
117
+            }catch(e){}
118
+        })))
119
+
120
+        //initialize managed exporters
121
+        await Promise.all(this.exporters.map(ex => ex['initialize']?ex['initialize']():undefined))
122
+
123
+        //initialize spec table
124
+        await this.admin.knex('specs').insert(getSpecTableData()).catch(e => { console.log("skipping spec insertion") })
125
+
126
+        //start rankServers
127
+        const rankServers : any = {}   
128
+
129
+        for(let i = 0; i < _Rank.length; i++){
130
+            const rank:Rank = _Rank[i]
131
+            const port = 20001 + i
132
+            const rankServer = await this.startRankServer(rank, port)
133
+            rankServers[rank] = {
134
+                server: rankServer,
135
+                port: port,
136
+                allowed: []
137
+            }
138
+        }
139
+        this.rankServers = rankServers
140
+    
141
+        setInterval(this.checkExpiredSessions, 600_000)        
142
+    }
143
+
144
+    checkExpiredSessions = () => {
145
+        Object.values(this.userLogins).map(userLogin => {
146
+            const auth = userLogin.auth
147
+            if(!this.checkToken(auth.token.value, auth.user.rank)){
148
+                this.logout(auth.user.name, auth.token.value)
149
+            }
150
+        })
151
+    }
152
+
153
+    checkConnection = async (socket: Socket) => {
154
+        let data : Auth | false = false
155
+        while(!data){
156
+            data = await Promise.race([socket.call('getUserData'), new Promise((res, rej) => { setTimeout(res, 250);})])
157
+        } 
158
+        this.userLogins[data.user.name].connections[socket.port] = socket
159
+        await socket.call('navigate', ["/frontcraft/dashboard"])
160
+        return true
161
+    }
162
+
163
+    setPermission = async (permission: RPCPermission) => {
164
+        await this.admin.knex('rpcpermissions')
165
+        .where('rpcname', '=', permission.name)
166
+        .update(permission)
167
+    }
168
+
169
+    getPermissions = async () : Promise<RPCPermission[]> => {
170
+        return await this.admin.knex.select('*').from('rpcpermissions')
171
+    }
172
+
173
+    getPermission = async (feature: keyof FrontcraftFeatureIfc, rank:Rank) : Promise<boolean> => {
174
+        const perm : RPCPermission[] = await this.admin.knex
175
+            .select(rank)
176
+            .from('rpcpermissions')
177
+            .where('name', '=', <string>feature)
178
+
179
+        if(perm.length === 0) return false
180
+        return perm[0][rank]
181
+    }
182
+
183
+    getRPCForRank = async (rank: Rank): Promise<AnyRPCExporter[]> => {
184
+        return [
185
+            ...this.exportRPCFeatures(), 
186
+            ...this.exporters.flatMap((exp) => exp.exportRPCFeatures())
187
+        ].filter(async (feature) => await this.getPermission(<keyof FrontcraftFeatureIfc> feature.name, rank))
188
+    }
189
+
190
+    createUser = async(user:User): Promise<User> => {
191
+        await this.admin.knex('users')
192
+        .insert(user)
193
+        
194
+        const users = await this.admin.knex
195
+        .select("*")
196
+        .from('users')
197
+        .where(user)
198
+        return users[0]
199
+    }
200
+
201
+    logout = async (username:string, tokenValue : string) : Promise<void> => {
202
+        try{
203
+
204
+            await Promise.all (Object.values(this.userLogins[username].connections).map(async (sock) => {
205
+                await sock.call('navigate', '/auth/login')
206
+            }))
207
+
208
+            Object.values(this.rankServers)
209
+                .forEach(state => {
210
+                    state.allowed = state.allowed.filter(allowed => allowed !== tokenValue)
211
+            })
212
+
213
+            delete this.userLogins[username]
214
+        }catch(e){
215
+            console.log(e)
216
+        }
217
+    }
218
+
219
+    login = async(username:string, pwHash:string) : Promise<Auth> => {
220
+        const res:User[] = await this.admin.knex
221
+        .select('*')
222
+        .from('users')
223
+        .where({ name: username })
224
+
225
+        if(res.length > 0 && pwHash === res[0].pwhash){
226
+            const user:User = res[0]
227
+            delete user.pwhash
228
+
229
+            //return existing auth
230
+            if(this.userLogins[username] != null){
231
+                return this.userLogins[username].auth
232
+            }
233
+            
234
+            const token = this.createToken(user)
235
+            const userAuth : Auth = {
236
+                token: token,
237
+                user: user,
238
+                port: this.rankServers[user.rank].port
239
+            }
240
+
241
+            this.userLogins[user.name] = {connections: {}, auth: userAuth}
242
+            this.rankServers[user.rank].allowed.push(token.value)
243
+
244
+            return userAuth 
245
+        }
246
+
247
+        throw new Error('login failed')
248
+    }
249
+
250
+    getAuth = async (tokenValue:string) : Promise<Auth> => {
251
+        const maybeAuth = Object.values(this.userLogins).find(login => login.auth.token.value === tokenValue)
252
+        if(maybeAuth)
253
+            return maybeAuth.auth
254
+
255
+        throw new Error("Bad token")
256
+    }
257
+
258
+    startRankServer = async (rank : Rank, port: number) : Promise<RPCServer> => {
259
+        const allowedRPCs = await this.getRPCForRank(rank)
260
+        let rpcServer : RPCServer = new RPCServer(port, allowedRPCs, {
261
+            closeHandler: (socket) => { 
262
+                Object.values(this.userLogins)
263
+                    .forEach(login => delete login.connections[socket.port])
264
+
265
+            },
266
+            connectionHandler: (socket) => {
267
+                this.checkConnection(socket).catch(() => {}) //sometimes times out if you go too fast
268
+            },
269
+            sesame: (sesame) => this.checkToken(sesame, rank)
270
+        })
271
+        return rpcServer
272
+    }
273
+
274
+    checkToken = (token: string, rank: Rank) : boolean => this.rankServers[rank].allowed.includes(token)
275
+                                                       && Object.values(this.userLogins).find(login => login.auth.token.value === token)!.auth.token.created > Date.now() - ONE_WEEK
276
+
277
+    createToken = (user:User): Token => {
278
+        
279
+        if(this.userLogins[user.name]){
280
+            return this.userLogins[user.name].auth.token
281
+        }
282
+
283
+        const token:Token = {
284
+            value: uuid(),
285
+            user_id: user.id!,
286
+            created: Date.now()
287
+        }
288
+
289
+        return token
290
+    }
291
+}

+ 7
- 5
src/backend/Components/User/RPCInterface.ts 查看文件

@@ -1,13 +1,15 @@
1
-import { Token, Auth, User, RPCPermission } from "../../Types/Types"
1
+import { Token, Auth, User, RPCPermission, Rank } from "../../Types/Types"
2 2
 
3
-export type UserManagerIfc = {
3
+export type LoginManagerIfc = {
4 4
     Authenticator: {
5
-        login: (username:string, pwHash:string) => Promise<Token>
6
-        authenticate: (token:string | Token) => Promise<Auth>
5
+        login: (username:string, pwHash:string) => Promise<Auth>
6
+        logout: (username: string, tokenValue :string) => Promise<void>
7
+        getAuth: (tokenValue: string) => Promise<Auth>
8
+        checkToken: (token: string, rank: Rank) => Promise<boolean>
7 9
     }
8 10
 }
9 11
 
10
-export type UserManagerFeatureIfc = {
12
+export type LoginManagerFeatureIfc = {
11 13
     createUser: {
12 14
         createUser: (user:User) => Promise<User>
13 15
     }

+ 0
- 227
src/backend/Components/User/UserManager.ts 查看文件

@@ -1,227 +0,0 @@
1
-import { RPCServer } from "rpclibrary";
2
-import { TableDefiniton, AnyRPCExporter, User, RPCPermission, _Rank, Token, Auth, Rank, FrontcraftFeatureIfc } from "../../Types/Types";
3
-import { FrontworkAdmin } from "../../Admin/Admin";
4
-import { PrivilegedRPCExporter } from "../../Types/PrivilegedRPCExporter";
5
-import { FrontworkComponent } from "../../Types/FrontworkComponent";
6
-import { getSpecTableData } from "../../Types/PlayerSpecs"
7
-import { UserManagerIfc, UserManagerFeatureIfc } from "./RPCInterface";
8
-const uuid = require('uuid/v4')
9
-
10
-export class LoginManager
11
-implements FrontworkComponent<UserManagerIfc, UserManagerFeatureIfc>{
12
-    name = "Authenticator" as "Authenticator";   
13
-    admin:FrontworkAdmin
14
-    
15
-    constructor(
16
-        private exporters: PrivilegedRPCExporter[]
17
-    ){}
18
-
19
-    exportRPCs = () => [
20
-        {
21
-            name: 'login' as 'login',
22
-            call: async (username:string, pwHash:string) => await this.login(username, pwHash) 
23
-        },{
24
-            name: 'authenticate' as 'authenticate',
25
-            call: async (tokenValue:string | Token) => await this.authenticate(tokenValue)
26
-        }
27
-    ]
28
-
29
-    onSetAdmin(admin:FrontworkAdmin){
30
-        this.exporters.forEach(e => e['admin'] = admin)
31
-    }
32
-
33
-    exportRPCFeatures = () => [
34
-        {
35
-            name: 'createUser' as 'createUser',
36
-            exportRPCs: () => [{
37
-                name: 'createUser' as 'createUser',
38
-                call: async (user:User) => await this.createUser(user)
39
-            }]
40
-        },{
41
-            name: 'modifyPermissions' as 'modifyPermissions',
42
-            exportRPCs: () => [{
43
-                name: 'getPermissions' as 'getPermissions',
44
-                call: async () => await this.getPermissions() 
45
-            },{
46
-                name: 'setPermission' as 'setPermission',
47
-                call: async (perm: RPCPermission) => await this.setPermission(perm) 
48
-            }]
49
-        }
50
-    ]
51
-    
52
-    
53
-    getTableDefinitions = (): TableDefiniton[] => [
54
-        {
55
-            name: 'users',
56
-            tableBuilder: (table) => {
57
-                table.increments("id").primary()
58
-                table.string("name").notNullable().unique()
59
-                table.string("pwhash").notNullable()
60
-                table.string("rank").notNullable()
61
-                table.string("email").nullable().unique()
62
-            }
63
-        },{
64
-            name: 'characters',
65
-            tableBuilder: (table) => {
66
-                table.increments("id").primary()
67
-                table.string("name").notNullable().unique()
68
-                table.integer("specid").notNullable()
69
-                table.foreign("specid").references("specs.id")
70
-                table.integer("userid").notNullable()
71
-                table.foreign("userid").references("users.id")
72
-            }
73
-        },{
74
-            name: 'rpcpermissions',
75
-            tableBuilder: (table) => {
76
-                table.string("name").primary().notNullable()
77
-                table.boolean("ADMIN").defaultTo(true).notNullable()
78
-                _Rank.forEach(r => table.boolean(r).defaultTo(false).notNullable())
79
-            }
80
-        },{
81
-            name: 'tokens',
82
-            tableBuilder: (table) => {
83
-                table.string('value').primary()
84
-                table.integer('user_id').notNullable()
85
-                table.foreign('user_id').references('users')
86
-                table.dateTime('created').defaultTo(this.admin.knex.fn.now())
87
-            }
88
-        },{
89
-            name: 'specs',
90
-            tableBuilder: (table) => {
91
-                table.string('class')
92
-                table.string('name')
93
-                table.primary(['class', 'name'])
94
-            }
95
-        }
96
-        ,...this.exporters.flatMap(exp => exp['getTableDefinitions']?exp['getTableDefinitions']():undefined)
97
-    ]
98
-    
99
-
100
-    async initialize(){
101
-        await Promise.all( 
102
-            [this, ...this.exporters].flatMap(exp => exp.exportRPCFeatures().map(async (feature) => {
103
-            try{
104
-                await this.admin.knex.insert({ name: feature.name }).into('rpcpermissions')
105
-            }catch(e){}
106
-        })))
107
-
108
-        await Promise.all(this.exporters.map(ex => ex['initialize']?ex['initialize']():undefined))
109
-
110
-        await this.admin.knex('specs').insert(getSpecTableData()).catch(e => { console.log("skipping spec insertion") })
111
-    }
112
-
113
-    async setPermission(permission: RPCPermission){
114
-        await this.admin.knex('rpcpermissions')
115
-        .where('rpcname', '=', permission.name)
116
-        .update(permission)
117
-    }
118
-
119
-    getPermissions = async () : Promise<RPCPermission[]> => {
120
-        return await this.admin.knex.select('*').from('rpcpermissions')
121
-    }
122
-
123
-    getPermission = async (feature: keyof FrontcraftFeatureIfc, rank:Rank) : Promise<boolean> => {
124
-        const perm : RPCPermission[] = await this.admin.knex
125
-            .select(rank)
126
-            .from('rpcpermissions')
127
-            .where('name', '=', <string>feature)
128
-
129
-        if(perm.length === 0) return false
130
-        return perm[0][rank]
131
-    }
132
-
133
-    getRPCForUser = async (user:User): Promise<AnyRPCExporter[]> => {
134
-        return [
135
-            ...this.exportRPCFeatures(), 
136
-            ...this.exporters.flatMap((exp) => exp.exportRPCFeatures())
137
-        ].filter(async (feature) => await this.getPermission(<keyof FrontcraftFeatureIfc> feature.name, user.rank))
138
-    }
139
-
140
-    createUser = async(user:User): Promise<User> => {
141
-        await this.admin.knex('users')
142
-        .insert(user)
143
-        
144
-        const users = await this.admin.knex
145
-        .select("*")
146
-        .from('users')
147
-        .where(user)
148
-        return users[0]
149
-    }
150
-
151
-    login = async(username:string, pwHash:string) : Promise<Token> => {
152
-        const res:User[] = await this.admin.knex
153
-        .select("*")
154
-        .from('users')
155
-        .where({ name: username })
156
-        
157
-        if(res.length > 0 && pwHash === res[0].pwhash){
158
-            return await this.createToken(res[0])
159
-        }
160
-
161
-        throw new Error('login failed')
162
-    }
163
-
164
-    authenticate = async(tokenValue: string | Token) : Promise<Auth> => {
165
-        if(typeof tokenValue !== 'string') tokenValue = tokenValue.value 
166
-
167
-        const res : User[] = await this.admin.knex
168
-            .select('users.id', 'name', 'specid', 'rank', 'email')
169
-            .from('tokens')
170
-            .join('users', function(){
171
-                this.on('users.id', '=', 'tokens.user_id')
172
-            })
173
-            .where({ value: tokenValue})
174
-
175
-        if(res.length === 0)
176
-            throw new Error('authentication failed')
177
-
178
-        const allowedRPCs = await this.getRPCForUser(res[0])
179
-        const randomPort = 20000 + Math.floor(Math.random() * 10000)
180
-        while(true){
181
-            try{
182
-                let commSock = new RPCServer(randomPort, allowedRPCs, {
183
-                    closeHandler: () => { 
184
-                        console.log(res[0].name, 'disconnected')
185
-                        commSock.destroy() 
186
-                    },
187
-                    connectionHandler: () => {
188
-                        console.log(res[0].name, 'connected')
189
-                        cancelTimeout()
190
-                    },
191
-                    sesame: tokenValue
192
-                })
193
-                const timeout = setTimeout(() => {
194
-                    console.log(res[0].name, 'timeout')
195
-                    commSock.destroy()
196
-                }, 10000)
197
-                const cancelTimeout = () => { clearTimeout(timeout) }
198
-                break;
199
-            }catch(e){
200
-                //retry
201
-            }
202
-        }
203
-
204
-        return {
205
-            port: randomPort,
206
-            user: res[0],
207
-            token: {
208
-                value: tokenValue,
209
-                user_id: res[0].id!
210
-            }
211
-        }
212
-    }
213
-
214
-    createToken = async(user:User): Promise<Token> => {
215
-        const old = await this.admin.knex.select('*').from('tokens').where({user_id: user.id})
216
-        if(old.length === 0){
217
-            const token:Token = {
218
-                value: uuid(),
219
-                user_id: user.id!
220
-            }
221
-            await this.admin.knex('tokens').insert(token)
222
-            return token
223
-        }else{
224
-            return old[0]
225
-        }
226
-    }
227
-}

+ 3
- 3
src/backend/Launcher.ts 查看文件

@@ -1,7 +1,7 @@
1 1
 import { FrontworkAdmin } from './Admin/Admin'
2 2
 import { RaidManager } from "./Components/Raid/RaidManager";
3 3
 import { ItemManager } from "./Components/Item/ItemManager";
4
-import { LoginManager } from "./Components/User/UserManager";
4
+import { LoginManager } from "./Components/User/LoginManager";
5 5
 import { Debugger } from './Components/Debugger/Debugger';
6 6
 import { FrontworkComponent } from './Types/FrontworkComponent';
7 7
 import { GuildManager } from './Components/Guild/GuildManager';
@@ -10,13 +10,13 @@ require('events').EventEmitter.defaultMaxListeners = 0;
10 10
 let raidManager = new RaidManager()
11 11
 let itemManager = new ItemManager()
12 12
 let guildManager = new GuildManager()
13
-let userManager = new LoginManager([
13
+let loginManager = new LoginManager([
14 14
     raidManager,
15 15
     itemManager,
16 16
     guildManager
17 17
 ])
18 18
 
19
-let components:FrontworkComponent[] = [ guildManager, raidManager, itemManager, userManager ]
19
+let components:FrontworkComponent[] = [ guildManager, raidManager, itemManager, loginManager ]
20 20
 let dbg = new Debugger(components)
21 21
 
22 22
 

+ 7
- 8
src/backend/Types/Types.ts 查看文件

@@ -1,8 +1,7 @@
1 1
 import * as Knex from "knex"
2 2
 import { RPCExporter } from "rpclibrary";
3 3
 import { RaidManagerFeatureIfc } from "../Components/Raid/RPCInterface";
4
-import { ItemManagerIfc, ItemManagerFeatureIfc } from "../Components/Item/RPCInterface";
5
-import { UserManagerIfc, UserManagerFeatureIfc } from "../Components/User/RPCInterface";
4
+import { LoginManagerIfc, LoginManagerFeatureIfc } from "../Components/User/RPCInterface";
6 5
 
7 6
 
8 7
 export declare type NotificationSeverity = 'Info' | 'Important' | 'Error';
@@ -19,7 +18,7 @@ export type TableDefiniton = {
19 18
 }
20 19
 
21 20
 export type Rank =             "ADMIN" | "Guildmaster" | "Officer" | "Classleader" | "Raider" | "Trial" | "Social" | "Guest"
22
-export const _Rank : Rank[] = ["Guildmaster" , "Officer" , "Classleader" , "Raider" , "Trial" , "Social" , "Guest"]
21
+export const _Rank : Rank[] = ["ADMIN" , "Guildmaster" , "Officer" , "Classleader" , "Raider" , "Trial" , "Social" , "Guest"]
23 22
 export type Class =              "Warrior" | "Rogue" | "Hunter" | "Mage" | "Warlock" | "Priest" | "Shaman" | "Paladin" | "Druid"
24 23
 export const _Class : Class[] = ["Warrior" , "Rogue" , "Hunter" , "Mage" , "Warlock" , "Priest" , "Shaman" , "Paladin" , "Druid"]
25 24
 
@@ -56,17 +55,17 @@ export type Signup = {
56 55
 export type Token = {
57 56
     value: string
58 57
     user_id: number
59
-    created?: number
58
+    created: number
60 59
 }
61 60
 
62 61
 export type Auth = {port: number, user: User, token: Token}
63 62
 
64
-export type FrontcraftIfc = UserManagerIfc 
65
-                          & ItemManagerIfc
63
+export type FrontcraftIfc = LoginManagerIfc 
64
+                          //& ItemManagerIfc
66 65
 
67 66
 export type FrontcraftFeatureIfc = RaidManagerFeatureIfc 
68
-                                 & UserManagerFeatureIfc
69
-                                 & ItemManagerFeatureIfc
67
+                                 & LoginManagerFeatureIfc
68
+                                 //& ItemManagerFeatureIfc
70 69
 
71 70
 export type Spec = {
72 71
     id?: number,

+ 6
- 6
src/frontend/package-lock.json 查看文件

@@ -14269,9 +14269,9 @@
14269 14269
       "integrity": "sha512-ZYzRkETgBrdEGzL5JSKimvjI2CX7ioyZCkX2BpcfyjqI+079W0wHAyj5W4rIZMcDSOHgLZtgz1IdDi/vU77KEQ=="
14270 14270
     },
14271 14271
     "rpclibrary": {
14272
-      "version": "1.4.2",
14273
-      "resolved": "https://registry.npmjs.org/rpclibrary/-/rpclibrary-1.4.2.tgz",
14274
-      "integrity": "sha512-IFigD+65a9MM+1AgEH5nurUzCyMg9hawqtnOA67/PtTGP5GsnWxaFESKl03k8L/PQ8ptZkWcub/63bfxkrwyMw==",
14272
+      "version": "1.5.1",
14273
+      "resolved": "https://registry.npmjs.org/rpclibrary/-/rpclibrary-1.5.1.tgz",
14274
+      "integrity": "sha512-EN6wlifkFmEQrHhtbalS0i90ZBNbLNFcrQ8hTrcJLBuNMFab49R8i5fruoDLDJM88iPcx6eWccI6zLHK7Qkl2A==",
14275 14275
       "requires": {
14276 14276
         "bsock": "^0.1.9",
14277 14277
         "http": "0.0.0",
@@ -14279,9 +14279,9 @@
14279 14279
       },
14280 14280
       "dependencies": {
14281 14281
         "uuid": {
14282
-          "version": "3.3.3",
14283
-          "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
14284
-          "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ=="
14282
+          "version": "3.4.0",
14283
+          "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
14284
+          "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
14285 14285
         }
14286 14286
       }
14287 14287
     },

+ 1
- 1
src/frontend/package.json 查看文件

@@ -69,7 +69,7 @@
69 69
     "normalize.css": "6.0.0",
70 70
     "pace-js": "1.0.2",
71 71
     "roboto-fontface": "0.8.0",
72
-    "rpclibrary": "^1.4.2",
72
+    "rpclibrary": "^1.5.1",
73 73
     "rxjs": "6.5.2",
74 74
     "rxjs-compat": "6.3.0",
75 75
     "socicon": "3.0.5",

+ 2
- 2
src/frontend/src/app/@theme/components/footer/footer.component.ts 查看文件

@@ -4,9 +4,9 @@ import { Component } from '@angular/core';
4 4
   selector: 'ngx-footer',
5 5
   styleUrls: ['./footer.component.scss'],
6 6
   template: `
7
-    <span class="created-by">Created with <i class="fa fa-gamepad"></i> by <b><a href="https://frontcraft.me" target="_blank">FrontCraft</a></b></span>
7
+    <span class="created-by">Created with <i class="fa fa-gamepad"></i> by <b><a href="https://gitea.nitowa.xyz/explore/repos" target="_blank">nitowa.xyz</a></b></span>
8 8
     <div class="socials">
9
-      <a href="http://www.versioncontrol.me/" target="_blank" class="ion ion-social-github"></a>
9
+      <a href="https://gitea.nitowa.xyz/" target="_blank" class="ion ion-social-github"></a>
10 10
     </div>
11 11
   `,
12 12
 })

+ 1
- 4
src/frontend/src/app/@theme/components/header/header.component.html 查看文件

@@ -10,10 +10,7 @@
10 10
 <div class="header-container">
11 11
   <nb-actions size="small">
12 12
 
13
-    <nb-action class="control-item">
14
-      <nb-search type="rotate-layout"></nb-search>
15
-    </nb-action>
16
-    <nb-action class="control-item" icon="email-outline"></nb-action>
13
+    <nb-action class="control-item" icon="message-square-outline"></nb-action>
17 14
     <nb-action class="control-item" icon="bell-outline"></nb-action>
18 15
 
19 16
     <nb-action class="user-action">

+ 7
- 3
src/frontend/src/app/@theme/components/header/header.component.ts 查看文件

@@ -1,5 +1,5 @@
1 1
 import { Component, OnDestroy, OnInit } from '@angular/core';
2
-import { NbMediaBreakpointsService, NbMenuService, NbSidebarService, NbThemeService } from '@nebular/theme';
2
+import { NbMediaBreakpointsService, NbMenuService, NbSidebarService, NbThemeService, NbContextMenuComponent, NbMenuItem } from '@nebular/theme';
3 3
 
4 4
 import { LayoutService } from '../../../@core/utils';
5 5
 import { map, takeUntil } from 'rxjs/operators';
@@ -39,14 +39,14 @@ export class HeaderComponent implements OnInit, OnDestroy {
39 39
 
40 40
   currentTheme = 'dark';
41 41
 
42
-  userMenu = [ { title: 'Profile' }, { title: 'Log out' } ];
42
+  userMenu : NbMenuItem[] = [ { title: 'Profile' }, { title: 'Log out', url: '/auth/logout' } ];
43 43
 
44 44
   constructor(private sidebarService: NbSidebarService,
45 45
               private menuService: NbMenuService,
46 46
               private themeService: NbThemeService,
47 47
               private layoutService: LayoutService,
48 48
               private breakpointService: NbMediaBreakpointsService,
49
-              private loginService: LoginApiService
49
+              private loginService: LoginApiService,
50 50
              ) {}
51 51
 
52 52
   ngOnInit() {
@@ -92,6 +92,10 @@ export class HeaderComponent implements OnInit, OnDestroy {
92 92
     return false;
93 93
   }
94 94
 
95
+  logout() {
96
+    this.loginService.logout()
97
+  }
98
+
95 99
   navigateHome() {
96 100
     this.menuService.navigateHome();
97 101
     return false;

+ 1
- 0
src/frontend/src/app/@theme/layouts/index.ts 查看文件

@@ -1,3 +1,4 @@
1 1
 export * from './one-column/one-column.layout';
2 2
 export * from './two-columns/two-columns.layout';
3 3
 export * from './three-columns/three-columns.layout';
4
+export * from './one-column-no-sidebar/one-column.layout';

+ 9
- 0
src/frontend/src/app/@theme/layouts/one-column-no-sidebar/one-column.layout.scss 查看文件

@@ -0,0 +1,9 @@
1
+@import '../../styles/themes';
2
+@import '~bootstrap/scss/mixins/breakpoints';
3
+@import '~@nebular/theme/styles/global/breakpoints';
4
+
5
+@include nb-install-component() {
6
+  .menu-sidebar ::ng-deep .scrollable {
7
+    padding-top: nb-theme(layout-padding-top);
8
+  }
9
+}

+ 19
- 0
src/frontend/src/app/@theme/layouts/one-column-no-sidebar/one-column.layout.ts 查看文件

@@ -0,0 +1,19 @@
1
+import { Component } from '@angular/core';
2
+
3
+@Component({
4
+  selector: 'ngx-one-column-no-sidebar-layout',
5
+  styleUrls: ['./one-column.layout.scss'],
6
+  template: `
7
+    <nb-layout windowMode>
8
+ 
9
+      <nb-layout-column>
10
+        <ng-content select="router-outlet"></ng-content>
11
+      </nb-layout-column>
12
+
13
+      <nb-layout-footer fixed>
14
+        <ngx-footer></ngx-footer>
15
+      </nb-layout-footer>
16
+    </nb-layout>
17
+  `,
18
+})
19
+export class OneColumnNoSidebarLayoutComponent {}

+ 2
- 0
src/frontend/src/app/@theme/theme.module.ts 查看文件

@@ -33,6 +33,7 @@ import {
33 33
   OneColumnLayoutComponent,
34 34
   ThreeColumnsLayoutComponent,
35 35
   TwoColumnsLayoutComponent,
36
+  OneColumnNoSidebarLayoutComponent,
36 37
 } from './layouts';
37 38
 import { DEFAULT_THEME } from './styles/theme.default';
38 39
 import { COSMIC_THEME } from './styles/theme.cosmic';
@@ -61,6 +62,7 @@ const COMPONENTS = [
61 62
   OneColumnLayoutComponent,
62 63
   ThreeColumnsLayoutComponent,
63 64
   TwoColumnsLayoutComponent,
65
+  OneColumnNoSidebarLayoutComponent
64 66
 ];
65 67
 const PIPES = [
66 68
   CapitalizePipe,

+ 0
- 5
src/frontend/src/app/app-routing.module.ts 查看文件

@@ -2,11 +2,6 @@ import { ExtraOptions, RouterModule, Routes } from '@angular/router';
2 2
 import { NgModule } from '@angular/core';
3 3
 
4 4
 const routes: Routes = [
5
-  {
6
-    path: 'pages',
7
-    loadChildren: () => import('./demo_pages/pages.module')
8
-      .then(m => m.PagesModule),
9
-  },
10 5
   {
11 6
     path: 'auth',
12 7
     loadChildren: () => import('./frontcraft/auth/auth.module')

+ 4
- 2
src/frontend/src/app/app.module.ts 查看文件

@@ -20,6 +20,7 @@ import {
20 20
   NbToastrModule,
21 21
   NbWindowModule,
22 22
 } from '@nebular/theme';
23
+import { LoginApiService } from './frontcraft/services/login-api';
23 24
 
24 25
 @NgModule({
25 26
   declarations: [AppComponent],
@@ -28,9 +29,7 @@ import {
28 29
     BrowserAnimationsModule,
29 30
     HttpClientModule,
30 31
     AppRoutingModule,
31
-
32 32
     ThemeModule.forRoot(),
33
-
34 33
     NbSidebarModule.forRoot(),
35 34
     NbMenuModule.forRoot(),
36 35
     NbDatepickerModule.forRoot(),
@@ -43,6 +42,9 @@ import {
43 42
     CoreModule.forRoot(),
44 43
   ],
45 44
   bootstrap: [AppComponent],
45
+  providers: [
46
+    
47
+  ]
46 48
 })
47 49
 export class AppModule {
48 50
 }

+ 1
- 2
src/frontend/src/app/demo_pages/pages-menu.ts 查看文件

@@ -1,4 +1,3 @@
1 1
 import { NbMenuItem } from '@nebular/theme';
2 2
 
3
-export const MENU_ITEMS: NbMenuItem[] = [
4
-];
3
+export const MENU_ITEMS: NbMenuItem[] = [];

+ 1
- 0
src/frontend/src/app/demo_pages/pages-routing.module.ts 查看文件

@@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
3 3
 
4 4
 import { PagesComponent } from './pages.component';
5 5
 import { NotFoundComponent } from './miscellaneous/not-found/not-found.component';
6
+import { AuthComponent } from '../frontcraft/auth/auth-layout.component';
6 7
 
7 8
 const routes: Routes = [{
8 9
   path: '',

+ 15
- 6
src/frontend/src/app/frontcraft/auth/auth-layout.component.ts 查看文件

@@ -1,13 +1,22 @@
1
-import { Component } from '@angular/core';
1
+import { Component, OnInit } from '@angular/core';
2
+import { LoginApiService } from '../services/login-api';
3
+import { Router } from '@angular/router';
2 4
 
3 5
 @Component({
4 6
   selector: 'auth-layout',
5 7
   template: `
6
-    <ngx-one-column-layout>
7
-        <router-outlet></router-outlet>
8
-    </ngx-one-column-layout>
8
+    <ngx-one-column-no-sidebar-layout>
9
+      <router-outlet></router-outlet>
10
+    </ngx-one-column-no-sidebar-layout>
9 11
   `,
10 12
 })
11
-export class AuthComponent {
12
-    
13
+
14
+export class AuthComponent implements OnInit{
15
+  constructor(
16
+    private loginSvc : LoginApiService,
17
+    private router: Router  
18
+  ){}
19
+
20
+  ngOnInit(){
21
+  }
13 22
 }

+ 10
- 0
src/frontend/src/app/frontcraft/auth/auth-routing.module.ts 查看文件

@@ -2,12 +2,22 @@ import { NgModule } from '@angular/core';
2 2
 import { RouterModule, Routes } from '@angular/router';
3 3
 import { MyLoginComponent } from './login/login.component';
4 4
 import { AuthComponent } from './auth-layout.component';
5
+import { LogoutComponent } from './logout/logout.component';
6
+import { RegisterComponent } from './register/register.component';
5 7
 
6 8
 export const routes: Routes = [
7 9
     {
8 10
         path: '',
9 11
         component: AuthComponent,
10 12
         children: [
13
+            {
14
+                path: 'register',
15
+                component: RegisterComponent
16
+            },
17
+            {
18
+                path: 'logout',
19
+                component: LogoutComponent,
20
+            },
11 21
             {
12 22
                 path: '**',
13 23
                 component: MyLoginComponent,

+ 9
- 2
src/frontend/src/app/frontcraft/auth/auth.module.ts 查看文件

@@ -8,7 +8,9 @@ import {
8 8
   NbButtonModule,
9 9
   NbCheckboxModule,
10 10
   NbInputModule,
11
-  NbMenuModule
11
+  NbMenuModule,
12
+  NbCardModule,
13
+  NbSelectModule
12 14
 } from '@nebular/theme';
13 15
 import { MyAuthRoutingModule } from './auth-routing.module';
14 16
 import { MyLoginComponent } from './login/login.component';
@@ -17,6 +19,8 @@ import { ThemeModule } from '../../@theme/theme.module';
17 19
 import { DashboardModule } from '../../demo_pages/dashboard/dashboard.module';
18 20
 import { ECommerceModule } from '../../demo_pages/e-commerce/e-commerce.module';
19 21
 import { MiscellaneousModule } from '../../demo_pages/miscellaneous/miscellaneous.module';
22
+import { LogoutComponent } from './logout/logout.component';
23
+import { RegisterComponent } from './register/register.component';
20 24
 
21 25
 
22 26
 @NgModule({
@@ -34,9 +38,12 @@ import { MiscellaneousModule } from '../../demo_pages/miscellaneous/miscellaneou
34 38
     DashboardModule,
35 39
     ECommerceModule,
36 40
     MiscellaneousModule,
37
-
41
+    NbCardModule,
42
+    NbSelectModule
38 43
   ],
39 44
   declarations: [
45
+    RegisterComponent,
46
+    LogoutComponent,
40 47
     AuthComponent,
41 48
     MyLoginComponent
42 49
   ],

+ 9
- 44
src/frontend/src/app/frontcraft/auth/login/login.component.html 查看文件

@@ -1,19 +1,4 @@
1 1
 <h1 id="title" class="title">Login</h1>
2
-<p class="sub-title">Hello! Log in with your email.</p>
3
-
4
-<nb-alert *ngIf="showMessages.error && errors?.length && !submitted" outline="danger" role="alert">
5
-  <p class="alert-title"><b>Oh snap!</b></p>
6
-  <ul class="alert-message-list">
7
-    <li *ngFor="let error of errors" class="alert-message">{{ error }}</li>
8
-  </ul>
9
-</nb-alert>
10
-
11
-<nb-alert *ngIf="showMessages.success && messages?.length && !submitted" outline="success" role="alert">
12
-  <p class="alert-title"><b>Hooray!</b></p>
13
-  <ul class="alert-message-list">
14
-    <li *ngFor="let message of messages" class="alert-message">{{ message }}</li>
15
-  </ul>
16
-</nb-alert>
17 2
 
18 3
 <form (ngSubmit)="login()" #form="ngForm" aria-labelledby="title">
19 4
 
@@ -21,25 +6,16 @@
21 6
     <label class="label" for="input-email">Email address:</label>
22 7
     <input nbInput
23 8
            fullWidth
24
-           [(ngModel)]="user.email"
9
+           [(ngModel)]="user.name"
25 10
            #email="ngModel"
26
-           name="email"
11
+           name="name"
27 12
            id="input-email"
28
-           pattern=".+@.+\..+"
29
-           placeholder="Email address"
13
+           pattern=".+"
14
+           placeholder="Username"
30 15
            fieldSize="giant"
31 16
            autofocus
32
-           [status]="email.dirty ? (email.invalid  ? 'danger' : 'success') : ''"
33
-           [required]="true"
34
-           [attr.aria-invalid]="email.invalid && email.touched ? true : null">
35
-    <ng-container *ngIf="email.invalid && email.touched">
36
-      <p class="error-message" *ngIf="email.errors?.required">
37
-        Email is required!
38
-      </p>
39
-      <p class="error-message" *ngIf="email.errors?.pattern">
40
-        Email should be the real one!
41
-      </p>
42
-    </ng-container>
17
+           [required]="true">
18
+
43 19
   </div>
44 20
 
45 21
   <div class="form-control-group">
@@ -55,20 +31,9 @@
55 31
            fieldSize="giant"
56 32
            [status]="password.dirty ? (password.invalid  ? 'danger' : 'success') : ''"
57 33
            [required]="true"
58
-           [minlength]="6"
34
+           [minlength]="1"
59 35
            [maxlength]="15"
60 36
            [attr.aria-invalid]="password.invalid && password.touched ? true : null">
61
-    <ng-container *ngIf="password.invalid && password.touched ">
62
-      <p class="error-message" *ngIf="password.errors?.required">
63
-        Password is required!
64
-      </p>
65
-      <p class="error-message" *ngIf="password.errors?.minlength || password.errors?.maxlength">
66
-        Password should contains
67
-        from {{ 6 }}
68
-        to {{ 15 }}
69
-        characters
70
-      </p>
71
-    </ng-container>
72 37
   </div>
73 38
 
74 39
   <div class="form-control-group accept-group">
@@ -88,5 +53,5 @@
88 53
 
89 54
 
90 55
 <section class="another-action" aria-label="Register">
91
-  Don't have an account? <a class="text-link" routerLink="../register">Register</a>
92
-</section>
56
+  Don't have an account? <a class="text-link" routerLink="/auth/register">Register</a>
57
+</section>

+ 8
- 1
src/frontend/src/app/frontcraft/auth/login/login.component.ts 查看文件

@@ -25,7 +25,14 @@ export class MyLoginComponent implements OnInit{
25 25
   ){}
26 26
 
27 27
   ngOnInit(){
28
-      console.log(this.router)
28
+    this.loginApi.checkLogin().then(loggedin => {
29
+      if(loggedin){
30
+        this.router.navigateByUrl("/")
31
+      }
32
+    });
29 33
   }
30 34
 
35
+  login(){
36
+    this.loginApi.login(this.user.name, this.user.password)
37
+  }
31 38
 }

+ 1
- 0
src/frontend/src/app/frontcraft/auth/logout/logout.component.html 查看文件

@@ -0,0 +1 @@
1
+You will be redirected shortly. If not click here <a href="/auth/login">click</a>

+ 19
- 0
src/frontend/src/app/frontcraft/auth/logout/logout.component.ts 查看文件

@@ -0,0 +1,19 @@
1
+import { Component, OnInit } from '@angular/core';
2
+import { LoginApiService } from '../../services/login-api';
3
+import { Router } from '@angular/router';
4
+
5
+@Component({
6
+  selector: 'mylogout',
7
+  templateUrl: './logout.component.html',
8
+})
9
+export class LogoutComponent implements OnInit{
10
+
11
+  constructor(
12
+    private loginApi : LoginApiService
13
+  ){}
14
+
15
+  ngOnInit(){
16
+    this.loginApi.logout()
17
+  }
18
+
19
+}

+ 89
- 0
src/frontend/src/app/frontcraft/auth/register/register.component.html 查看文件

@@ -0,0 +1,89 @@
1
+<h1 id="title" class="title">Account application</h1>
2
+
3
+<form (ngSubmit)="register()" #form="ngForm" aria-labelledby="title">
4
+
5
+  <div class="form-control-group">
6
+    <label class="label" for="input-email">Email address:</label>
7
+    <input nbInput
8
+           fullWidth
9
+           [(ngModel)]="user.email"
10
+           #email="ngModel"
11
+           name="name"
12
+           id="input-email"
13
+           pattern=".+"
14
+           placeholder="Email Address"
15
+           fieldSize="giant"
16
+           autofocus
17
+           [required]="true">
18
+  </div>
19
+
20
+  <div class="form-control-group">
21
+    <label class="label" for="input-username">Username:</label>
22
+    <input nbInput
23
+           fullWidth
24
+           [(ngModel)]="user.name"
25
+           #email="ngModel"
26
+           name="name"
27
+           id="input-username"
28
+           pattern=".+"
29
+           placeholder="Username"
30
+           fieldSize="giant"
31
+           autofocus
32
+           [required]="true">
33
+  </div>
34
+
35
+  <div class="form-control-group">
36
+    <label class="label" for="input-password">Password:</label>
37
+    <input nbInput
38
+           fullWidth
39
+           [(ngModel)]="user.password"
40
+           #password="ngModel"
41
+           name="password"
42
+           type="password"
43
+           id="input-password"
44
+           placeholder="Password"
45
+           fieldSize="giant"
46
+           [status]="password.dirty ? (password.invalid  ? 'danger' : 'success') : ''"
47
+           [required]="true"
48
+           [minlength]="1"
49
+           [maxlength]="15"
50
+           [attr.aria-invalid]="password.invalid && password.touched ? true : null">
51
+  </div>
52
+
53
+  <div class="form-control-group accept-group">
54
+    <nb-checkbox name="rememberMe" [(ngModel)]="user.rememberMe" *ngIf="rememberMe">Remember me</nb-checkbox>
55
+  </div>
56
+
57
+  <nb-card>
58
+    <nb-card-body>
59
+      <nb-checkbox [(ngModel)]="showApplication" name="amMember" #checkbox>I am already a member</nb-checkbox>
60
+      <br />
61
+      <span *ngIf="showApplication">
62
+        And my main is &nbsp; <input name="preMember" nbInput> with rank <nb-select placeholder="Rank">
63
+          <nb-option *ngFor="let rank of ranks" [value]=rank >{{rank}}</nb-option>
64
+          
65
+        </nb-select>
66
+      </span>
67
+      <span *ngIf="!showApplication">Hello my name is &nbsp; <input name="charName" nbInput>
68
+        I am playing a level 60  <input name="charName" placeholder="Spec + Class" nbInput> and I would like to join tranquil because
69
+        <textarea nbInput fullWidth placeholder="reason" style="min-height: 400px"></textarea>
70
+
71
+      </span>
72
+
73
+    </nb-card-body>
74
+  </nb-card>
75
+
76
+  <button nbButton
77
+          fullWidth
78
+          status="primary"
79
+          size="giant"
80
+          [disabled]="submitted || !form.valid"
81
+          [class.btn-pulse]="submitted">
82
+    Log In
83
+  </button>
84
+</form>
85
+
86
+
87
+<section class="another-action" aria-label="Register">
88
+  Back to <a class="text-link" routerLink="auth/login">Login</a>
89
+</section>

+ 32
- 0
src/frontend/src/app/frontcraft/auth/register/register.component.ts 查看文件

@@ -0,0 +1,32 @@
1
+import { Component, OnInit } from '@angular/core';
2
+import { LoginApiService } from '../../services/login-api';
3
+import { Router } from '@angular/router';
4
+import { _Rank } from '../../../../../../backend/Types/Types'
5
+
6
+@Component({
7
+  selector: 'register',
8
+  templateUrl: './register.component.html',
9
+})
10
+export class RegisterComponent implements OnInit{
11
+
12
+  user: any = {};
13
+  showApplication = false
14
+  ranks = _Rank
15
+
16
+  constructor(
17
+    private router : Router,  
18
+    private loginApi : LoginApiService
19
+  ){}
20
+
21
+  ngOnInit(){
22
+    this.loginApi.checkLogin().then(loggedin => {
23
+      if(loggedin){
24
+        this.router.navigateByUrl("/")
25
+      }
26
+    });
27
+  }
28
+
29
+  login(){
30
+    this.loginApi.login(this.user.name, this.user.password)
31
+  }
32
+}

+ 25
- 10
src/frontend/src/app/frontcraft/pages/pages-layout.component.ts 查看文件

@@ -1,8 +1,10 @@
1
-import { Component } from '@angular/core';
1
+import { Component, AfterContentChecked, OnInit } from '@angular/core';
2 2
 import { NbMenuItem } from '@nebular/theme';
3
+import { LoginApiService } from '../services/login-api';
4
+import { Router } from '@angular/router';
3 5
 
4 6
 @Component({
5
-  selector: 'auth-layout',
7
+  selector: 'page-layout',
6 8
   template: `
7 9
 
8 10
     <ngx-one-column-layout>
@@ -12,12 +14,25 @@ import { NbMenuItem } from '@nebular/theme';
12 14
     </ngx-one-column-layout>
13 15
   `,
14 16
 })
15
-export class PagesLayoutComponent {
16
-    menu:NbMenuItem[] = [{
17
-      icon: 'cube-outline',
18
-      title: 'test'
19
-    },{
20
-      icon: 'globe-2-outline',
21
-      title: 'test2'
22
-    }]
17
+export class PagesLayoutComponent implements OnInit{
18
+  menu:NbMenuItem[] = [{
19
+    icon: 'people-outline',
20
+    title: 'People'
21
+  },{
22
+    icon: 'clock-outline',
23
+    title: 'Raids'
24
+  }]
25
+  
26
+  constructor(
27
+    private loginSvc : LoginApiService,
28
+    private router : Router
29
+  ){}
30
+
31
+  ngOnInit() : void {
32
+    this.loginSvc.checkLogin().then(loggedin => {
33
+      if(!loggedin){
34
+        this.router.navigateByUrl("/auth")
35
+      }
36
+    });
37
+  }
23 38
 }

+ 4
- 2
src/frontend/src/app/frontcraft/pages/pages.module.ts 查看文件

@@ -8,7 +8,8 @@ import {
8 8
   NbButtonModule,
9 9
   NbCheckboxModule,
10 10
   NbInputModule,
11
-  NbMenuModule
11
+  NbMenuModule,
12
+  NbCardModule
12 13
 } from '@nebular/theme';
13 14
 import { MyAuthRoutingModule } from './pages-routing.module';
14 15
 import { FrontcraftDashboardComponent } from './dashboard/dashboard.component';
@@ -34,7 +35,8 @@ import { MiscellaneousModule } from '../../demo_pages/miscellaneous/miscellaneou
34 35
     DashboardModule,
35 36
     ECommerceModule,
36 37
     MiscellaneousModule,
37
-
38
+    NbCardModule,
39
+    
38 40
   ],
39 41
   declarations: [
40 42
     PagesLayoutComponent,

+ 74
- 54
src/frontend/src/app/frontcraft/services/login-api.ts 查看文件

@@ -1,8 +1,9 @@
1
-import { Injectable } from "@angular/core";
1
+import { Injectable, Injector, NgZone } from "@angular/core";
2 2
 import {RPCSocket} from 'rpclibrary/js/src/Frontend'
3 3
 
4 4
 import { Token, Auth, User, _Class, FrontcraftFeatureIfc, SomeOf, FrontcraftIfc, } from '../../../../../backend/Types/Types'
5 5
 import { CookieService } from 'ngx-cookie-service';
6
+import { Router } from '@angular/router';
6 7
 
7 8
 @Injectable()
8 9
 export class LoginApiService{
@@ -11,93 +12,112 @@ export class LoginApiService{
11 12
     private privSocket: RPCSocket & SomeOf<FrontcraftFeatureIfc>
12 13
 
13 14
     constructor(
14
-        private cookieSvc : CookieService
15
+        private injector: Injector,
16
+        private cookieSvc : CookieService,
17
+        private ngZone : NgZone
15 18
     ){}
16 19
 
17 20
     getUnprivilegedSocket = () : RPCSocket & FrontcraftIfc => this.socket
18 21
 
19 22
     private getPrivilegedSocket = async (auth:Auth) : Promise<RPCSocket & SomeOf<FrontcraftFeatureIfc>> => {
20 23
         if(this.privSocket) return this.privSocket
21
-        if(auth.user.rank === 'Guest'){
22
-            return await RPCSocket.makeSocket<SomeOf<FrontcraftFeatureIfc>>(
23
-                20001,
24
-                window.location.hostname
25
-            )
26
-        }
24
+        if(auth == null) throw new Error("Bad Auth")
25
+
27 26
         try{
28
-            const sock = await RPCSocket.makeSocket<SomeOf<FrontcraftFeatureIfc>>(
27
+            const sock = new RPCSocket(
29 28
                 auth.port, 
30
-                window.location.hostname, 
31
-                auth.token.value
29
+                window.location.hostname
32 30
             )
31
+           
32
+            const authSock = await sock.connect<RPCSocket & SomeOf<FrontcraftFeatureIfc>>(auth.token.value)
33
+
34
+            sock.hook('kick', () => this.logout())
35
+            sock.hook('getUserData', () => auth)
36
+            sock.hook('navigate', (where:string) => {
37
+                this.ngZone.run(() => {
38
+                    this.injector.get(Router).navigateByUrl(where);
39
+                })
40
+            })
41
+            sock.on('error', (e) => {
42
+                console.log('Socket error',  e)
43
+                this.logout()
44
+            })
33 45
 
34
-            //login success
35 46
             this.auth = auth
36
-            this.privSocket = sock
37
-            this.setCookie(auth.token)
38
-            return sock
47
+            this.privSocket = authSock
48
+            this.cookieSvc.set('token', JSON.stringify(auth))
49
+            return authSock
39 50
         }catch(e){ 
40 51
             //login failed
41
-            throw new Error('login failed')
52
+            throw new Error(e)
42 53
         }
43 54
     }
44 55
 
45 56
     getFeature = async <K extends keyof FrontcraftFeatureIfc>(feature : K) : Promise<void | FrontcraftFeatureIfc[K]> => {
46
-        const sock = await this.getPrivilegedSocket(this.auth)
47
-        if(sock[feature]) return <FrontcraftFeatureIfc[K]> sock[feature]
57
+        try{
58
+            const sock = await this.getPrivilegedSocket(this.auth)
59
+            if(sock[feature]) return <FrontcraftFeatureIfc[K]> sock[feature]
60
+        }catch(e){
61
+            return
62
+        }
48 63
     }
49 64
 
50
-    getCurrentUser = () : User => this.auth 
51
-                                ? this.auth.user 
52
-                                : { 
53
-                                    name: 'Guest', 
54
-                                    specid: 1, 
55
-                                    pwhash: '',
56
-                                    rank: 'Guest'
57
-                                }
58
-
59
-    authenticate = async (token : Token | string) : Promise<RPCSocket & SomeOf<FrontcraftFeatureIfc>> => {
60
-        const auth = await this.socket.Authenticator.authenticate(token)
61
-        return await this.getPrivilegedSocket(auth)
62
-    }
65
+    getCurrentUser = () : User | undefined => this.auth?this.auth.user:undefined
63 66
 
64
-    login = async (username: string, password: string) => {
67
+    login = async (username: string, password: string) : Promise<RPCSocket & SomeOf<FrontcraftFeatureIfc>> => {
65 68
         const buf = str2arraybuf(password)
66 69
         const pwHash = await crypto.subtle.digest('SHA-256', buf);
67
-        const token = await this.socket.Authenticator.login(username, buf2hex(pwHash))
68
-        return await this.authenticate(token)
70
+
71
+        const auth = await this.socket.Authenticator.login(username, buf2hex(pwHash))
72
+
73
+
74
+        if(!auth){ 
75
+            await this.logout()
76
+            throw new Error("Login failed")
77
+        }
78
+        const sock = await this.getPrivilegedSocket(auth)
79
+        return sock
69 80
     }
70 81
 
71
-    logout = () => {
72
-        this.cookieSvc.deleteAll()
73
-        this.auth  = null
82
+    logout = async () => {
83
+        this.cookieSvc.set('token', undefined)
84
+        if(this.auth) await this.socket.Authenticator.logout(this.auth.user.name, this.auth.token.value)
74 85
         if(this.privSocket) this.privSocket.destroy()
75 86
         this.privSocket = null
87
+        this.auth  = null
88
+        this.ngZone.run(() => {
89
+            this.injector.get(Router).navigate(['/auth']);
90
+        })
76 91
     }
77 92
 
78
-    private setCookie = (token: string | Token) => {
79
-        token = 'string' === typeof token? token : token.value 
80
-        this.cookieSvc.set('token', token)
81
-    }
82
-
83
-    private getCookie = () : string | Token | undefined => {
84
-        return this.cookieSvc.get('token')
85
-    }
86 93
 
87
-    initialize = async () : Promise<RPCSocket> => {
94
+    initialize = async () : Promise<any> => {
88 95
         const sock = await RPCSocket.makeSocket<FrontcraftIfc>(20000, window.location.hostname)
89 96
         this.socket = sock
90
-
91
-        const cookie = this.getCookie()
92
-        if(cookie) {
93
-            try{
94
-                return await this.authenticate(cookie)
95
-            }catch(e){
96
-                this.logout()
97
+        try{
98
+            const cookie = JSON.parse(this.cookieSvc.get('token'))
99
+            if(cookie != null) {
100
+                try{
101
+                    const auth = await sock.Authenticator.getAuth(cookie.token.value)
102
+                    if(!auth) return sock
103
+                    return await this.getPrivilegedSocket(auth)
104
+                }catch(e){
105
+                    await this.logout()
106
+                    return
107
+                }
97 108
             }
109
+        }catch(e){
98 110
         }
111
+    }
99 112
 
100
-        return sock
113
+    getAuth = () => this.auth
114
+
115
+    async checkLogin() : Promise<boolean>{
116
+        if(!this.auth) return false
117
+        const valid = await this.socket.Authenticator.checkToken(this.auth.token.value, this.auth.user.rank)
118
+        if(valid) return true
119
+        await this.logout()
120
+        return false
101 121
     }
102 122
 }
103 123
 

正在加载...
取消
保存