XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

SpaceNinjaServer

A simple server for a small space ninja game

公开
关注 0 Fork 1 Star 0
返回提交历史

XFEstudio/SpaceNinjaServer

feat: indirect authentication for game websocket connection (#3409)

Reviewed-on: https://onlyg.it/OpenWF/SpaceNinjaServer/pulls/3409 Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com> Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>

904387d5
Sainan <63328889+Sainan@users.noreply.github.com>
提交于

代码差异

5 个文件 +45 -26
Modified src/controllers/api/loginController.ts +1 -0
@@ -113,6 +113,7 @@ export const loginController: RequestHandler = async (request, response) => {
113 113 account.CountryCode = loginRequest.lang?.toUpperCase() ?? "EN";
114 114 account.BuildLabel = buildLabel;
115 115 account.LastLogin = new Date();
116 account.Dropped = undefined;
116 117 await account.save();
117 118
118 119 handleNonceInvalidation(account._id.toString());
Modified src/controllers/custom/getAccountInfoController.ts +1 -1
@@ -4,7 +4,7 @@ import { getAccountForRequest, isAdministrator } from "../../services/loginServi
4 4 import type { RequestHandler } from "express";
5 5
6 6 export const getAccountInfoController: RequestHandler = async (req, res) => {
7 const account = await getAccountForRequest(req, true);
7 const account = await getAccountForRequest(req, "IRC");
8 8 const inventory = await getInventory(account._id.toString(), "QuestKeys");
9 9 const info: IAccountInfo = {
10 10 DisplayName: account.DisplayName,
Modified src/controllers/custom/ircDroppedController.ts +1 -1
@@ -2,7 +2,7 @@ import type { RequestHandler } from "express";
2 2 import { getAccountForRequest } from "../../services/loginService.ts";
3 3
4 4 export const ircDroppedController: RequestHandler = async (req, res) => {
5 const account = await getAccountForRequest(req, true);
5 const account = await getAccountForRequest(req, "IRC");
6 6 account.Dropped = true;
7 7 await account.save();
8 8 res.end();
Modified src/services/loginService.ts +18 -9
@@ -10,6 +10,7 @@ import { config } from "./configService.ts";
10 10 import { createStats } from "./statsService.ts";
11 11 import crc32 from "crc-32";
12 12 import crypto from "node:crypto";
13 import { logger } from "../utils/logger.ts";
13 14
14 15 export const isCorrectPassword = (requestPassword: string, databasePassword: string): boolean => {
15 16 return requestPassword === databasePassword;
@@ -72,43 +73,51 @@ export const createPersonalRooms = async (accountId: Types.ObjectId, shipId: Typ
72 73 export type TAccountDocument = Document<unknown, {}, IDatabaseAccountJson> &
73 74 IDatabaseAccountJson & { _id: Types.ObjectId; __v: number };
74 75
75 export const getAccountForRequest = async (req: Request, acceptToken?: true): Promise<TAccountDocument> => {
76 if (!req.query.accountId) {
76 export const getAccountForQuery = async (
77 query: Record<string, string>,
78 acceptToken?: string
79 ): Promise<TAccountDocument> => {
80 if (!query.accountId) {
77 81 throw new Error("Request is missing accountId parameter");
78 82 }
79 83
80 84 // Tokens are specific to OpenWF to avoid sending the nonce (which gives full account access) over insecure transports.
81 if (acceptToken && req.query.token) {
82 const account = await Account.findById(req.query.accountId as string);
85 if (query.token && acceptToken == query.ct) {
86 const account = await Account.findById(query.accountId);
83 87 if (!account || !account.Nonce) {
84 88 throw new Error("Invalid accountId-token pair");
85 89 }
86 90 const token = crypto
87 91 .createHmac("sha256", account.Nonce.toString())
88 .update(`accountId=${req.query.accountId as string}&ct=${(req.query.ct as string | undefined) ?? ""}`)
92 .update(`accountId=${query.accountId}&ct=${(query.ct as string | undefined) ?? ""}`)
89 93 .digest("hex");
90 94 //console.log(`expected token: ${token}`);
91 if ((req.query.token as string).toLowerCase() != token) {
95 if (query.token.toLowerCase() != token) {
92 96 throw new Error("Invalid accountId-token pair");
93 97 }
94 98 return account;
95 99 }
96 100
97 const nonce: number = parseInt(req.query.nonce as string);
101 const nonce: number = parseInt(query.nonce);
98 102 if (!nonce) {
99 103 throw new Error("Request is missing nonce parameter");
100 104 }
101 const account = await Account.findById(req.query.accountId as string);
105 const account = await Account.findById(query.accountId);
102 106 if (!account || account.Nonce != nonce) {
103 107 throw new Error("Invalid accountId-nonce pair");
104 108 }
105 if (account.Dropped && req.query.ct) {
109 if (account.Dropped && query.ct) {
110 logger.debug(`removing dropped mark from ${query.accountId}`);
106 111 account.Dropped = undefined;
107 112 await account.save();
108 113 }
109 114 return account;
110 115 };
111 116
117 export const getAccountForRequest = (req: Request, acceptToken?: string): Promise<TAccountDocument> => {
118 return getAccountForQuery(req.query as Record<string, string>, acceptToken);
119 };
120
112 121 export const getAccountIdForRequest = async (req: Request): Promise<string> => {
113 122 return (await getAccountForRequest(req))._id.toString();
114 123 };
Modified src/services/wsService.ts +24 -15
@@ -3,7 +3,13 @@ import type https from "https";
3 3 import type { WebSocket } from "ws";
4 4 import { WebSocketServer } from "ws";
5 5 import { Account } from "../models/loginModel.ts";
6 import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "./loginService.ts";
6 import {
7 createAccount,
8 createNonce,
9 getAccountForQuery,
10 getUsernameFromEmail,
11 isCorrectPassword
12 } from "./loginService.ts";
7 13 import type { IDatabaseAccountJson } from "../types/loginTypes.ts";
8 14 import type { HydratedDocument } from "mongoose";
9 15 import { logError, logger } from "../utils/logger.ts";
@@ -62,10 +68,15 @@ interface IWsMsgFromClient {
62 68 password: string;
63 69 isRegister: boolean;
64 70 };
65 auth_game?: {
66 accountId: string;
67 nonce: number;
68 };
71 auth_game?:
72 | {
73 accountId: string;
74 nonce: string;
75 }
76 | {
77 accountId: string;
78 token: string;
79 };
69 80 logout?: boolean;
70 81 sync_inventory?: boolean;
71 82 }
@@ -168,16 +179,14 @@ const wsOnConnect = (ws: WebSocket, req: http.IncomingMessage): void => {
168 179 }
169 180 if (data.auth_game) {
170 181 (ws as IWsCustomData).isGame = true;
171 if (data.auth_game.nonce) {
172 const account: IDatabaseAccountJson | null = await Account.findOne({
173 _id: data.auth_game.accountId,
174 Nonce: data.auth_game.nonce
175 });
176 if (account) {
177 (ws as IWsCustomData).accountId = account.id;
178 logger.debug(`got bootstrapper connection for ${account.id}`);
179 sendWsBroadcastToWebui({ have_game_ws: true }, account.id);
180 }
182 try {
183 const account = await getAccountForQuery({ ...data.auth_game, ct: "WS" }, "WS");
184 const accountId = account._id.toString();
185 (ws as IWsCustomData).accountId = accountId;
186 logger.debug(`got bootstrapper connection for ${accountId}`);
187 sendWsBroadcastToWebui({ have_game_ws: true }, accountId);
188 } catch (e) {
189 /* empty */
181 190 }
182 191 }
183 192 if (data.logout) {