返回提交历史
Modified
src/controllers/api/loginController.ts
+1
-0
Modified
src/controllers/custom/getAccountInfoController.ts
+1
-1
Modified
src/controllers/custom/ircDroppedController.ts
+1
-1
Modified
src/services/loginService.ts
+18
-9
Modified
src/services/wsService.ts
+24
-15
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
代码差异
5 个文件
+45
-26
@@ -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());
@@ -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,
@@ -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();
@@ -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
};
@@ -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) {