Removed web project (galvanic authentication support in IL2CPP universal patch)
Moved instance ID to header User instances for profile management .. other stuff
This commit is contained in:
6
src/routes/account.ts
Normal file
6
src/routes/account.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { APIUtils } from "../apiutils.ts";
|
||||
import { route as AccountRoute } from "./account/account.ts";
|
||||
|
||||
export const route = APIUtils.createRouter('/accountservice');
|
||||
|
||||
route.router.use(AccountRoute.path, AccountRoute.router);
|
||||
30
src/routes/account/account.ts
Normal file
30
src/routes/account/account.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { APIUtils } from "../../apiutils.ts";
|
||||
import express from "express";
|
||||
import Profile from "../../data/profiles.ts";
|
||||
|
||||
export const route = APIUtils.createRouter("/account");
|
||||
|
||||
interface CreateAccountRequestBody {
|
||||
platform: string,
|
||||
platformId: string,
|
||||
deviceId: string
|
||||
}
|
||||
|
||||
route.router.post('/create',
|
||||
|
||||
APIUtils.UserAuthentication,
|
||||
express.urlencoded({ extended: true }),
|
||||
APIUtils.checkBodyTypes<CreateAccountRequestBody>({platform: "", platformId: "", deviceId: ""}),
|
||||
|
||||
async (_rq, rs) => {
|
||||
const newAcc = await Profile.init();
|
||||
|
||||
rs.locals.user.addAssociatedProfile(newAcc.getId());
|
||||
|
||||
rs.json({
|
||||
success: true,
|
||||
value: await newAcc.export()
|
||||
});
|
||||
},
|
||||
|
||||
);
|
||||
@@ -1,8 +1,10 @@
|
||||
import { route as VersionCheckRoute } from "./api/versioncheck.ts";
|
||||
import { route as ConfigRoute } from "./api/config.ts";
|
||||
import { route as GameConfig } from "./api/gameconfigs.ts";
|
||||
import { APIUtils } from "../apiutils.ts";
|
||||
|
||||
export const route = APIUtils.createRouter('/api');
|
||||
|
||||
route.router.use(VersionCheckRoute.path, VersionCheckRoute.router);
|
||||
route.router.use(ConfigRoute.path, ConfigRoute.router);
|
||||
route.router.use(ConfigRoute.path, ConfigRoute.router);
|
||||
route.router.use(GameConfig.path, GameConfig.router);
|
||||
@@ -5,9 +5,9 @@ export const route = APIUtils.createRouter('/versioncheck');
|
||||
const validVersion = '20191120';
|
||||
|
||||
enum VersionStatus {
|
||||
UpdateRequired,
|
||||
ValidForPlay,
|
||||
ValidForMenu,
|
||||
ValidForPlay
|
||||
UpdateRequired
|
||||
}
|
||||
type ValidVersionResponse = {
|
||||
VersionStatus: VersionStatus
|
||||
@@ -15,7 +15,8 @@ type ValidVersionResponse = {
|
||||
|
||||
route.router.get('/v4', (rq, rs) => {
|
||||
const requestedVer = rq.query['v'];
|
||||
if (typeof requestedVer == 'undefined') {
|
||||
const pQuery = rq.query['p'];
|
||||
if (typeof requestedVer == 'undefined' || typeof pQuery == 'undefined') {
|
||||
rs.statusCode = 400;
|
||||
rs.json(APIUtils.genericResponseFormat(true, 'One or more query parameters were not found.'));
|
||||
}
|
||||
|
||||
8
src/routes/auth.ts
Normal file
8
src/routes/auth.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { APIUtils } from "../apiutils.ts";
|
||||
import { route as CachedLoginRoute } from "./auth/cachedlogin.ts";
|
||||
import { route as ConnectRoute } from "./auth/connect.ts";
|
||||
|
||||
export const route = APIUtils.createRouter('/authservice');
|
||||
|
||||
route.router.use(CachedLoginRoute.path, CachedLoginRoute.router);
|
||||
route.router.use(ConnectRoute.path, ConnectRoute.router);
|
||||
15
src/routes/auth/cachedlogin.ts
Normal file
15
src/routes/auth/cachedlogin.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { APIUtils } from "../../apiutils.ts";
|
||||
|
||||
export const route = APIUtils.createRouter("/cachedlogin");
|
||||
|
||||
route.router.get('/forplatformid/:platformtype/:platformid',
|
||||
|
||||
APIUtils.UserAuthentication,
|
||||
|
||||
async (_rq, rs) => {
|
||||
|
||||
rs.json(await rs.locals.user.exportAssociatedProfiles());
|
||||
|
||||
}
|
||||
|
||||
);
|
||||
5
src/routes/auth/connect.ts
Normal file
5
src/routes/auth/connect.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { APIUtils } from "../../apiutils.ts";
|
||||
|
||||
export const route = APIUtils.createRouter("/connect");
|
||||
|
||||
//route.router.post()
|
||||
@@ -2,7 +2,7 @@ import { APIUtils } from "../apiutils.ts";
|
||||
import { Config } from "../config.ts";
|
||||
|
||||
const config = Config.getConfig() as Config.GalvanicConfiguration;
|
||||
const protocol = config.web.secureNameserverHost ? 'https' : 'http';
|
||||
const protocol = config.web.securepublichost ? 'https' : 'http';
|
||||
|
||||
export const route = APIUtils.createRouter('/ns');
|
||||
|
||||
@@ -21,17 +21,17 @@ type NameserverHosts = {
|
||||
}
|
||||
|
||||
const nameserver: NameserverHosts = {
|
||||
Auth: `${protocol}://${config.web.nameserverHost}/auth`,
|
||||
API: `${protocol}://${config.web.nameserverHost}`,
|
||||
WWW: `${protocol}://${config.web.nameserverHost}`,
|
||||
Notifications: `${protocol}://${config.web.nameserverHost}/notify`,
|
||||
Images: `${protocol}://${config.web.nameserverHost}/img`,
|
||||
CDN: `${protocol}://${config.web.nameserverHost}/cdn`,
|
||||
Commerce: `${protocol}://${config.web.nameserverHost}/commerce`,
|
||||
Matchmaking: `${protocol}://${config.web.nameserverHost}/match`,
|
||||
Storage: `${protocol}://${config.web.nameserverHost}/storage`,
|
||||
Chat: `${protocol}://${config.web.nameserverHost}/chat`,
|
||||
Leaderboard: `${protocol}://${config.web.nameserverHost}/leaderboard`
|
||||
Auth: `${protocol}://${config.web.publichost}/auth`,
|
||||
API: `${protocol}://${config.web.publichost}`,
|
||||
WWW: `${protocol}://${config.web.publichost}`,
|
||||
Notifications: `${protocol}://${config.web.publichost}/notify`,
|
||||
Images: `${protocol}://${config.web.publichost}/img`,
|
||||
CDN: `${protocol}://${config.web.publichost}/cdn`,
|
||||
Commerce: `${protocol}://${config.web.publichost}/commerce`,
|
||||
Matchmaking: `${protocol}://${config.web.publichost}/match`,
|
||||
Storage: `${protocol}://${config.web.publichost}/storage`,
|
||||
Chat: `${protocol}://${config.web.publichost}/chat`,
|
||||
Leaderboard: `${protocol}://${config.web.publichost}/leaderboard`
|
||||
}
|
||||
|
||||
route.router.get('*', (_rq, rs) => {
|
||||
|
||||
@@ -1,53 +1,107 @@
|
||||
import { APIUtils, getSrcIpDefault } from "../apiutils.ts";
|
||||
import { APIUtils, NoBody } from "../apiutils.ts";
|
||||
// @ts-types = "npm:@types/express"
|
||||
import express from "express";
|
||||
import { User } from "../data/users.ts";
|
||||
import Recaptcha from "../data/recaptcha.ts";
|
||||
import { Config } from "../config.ts";
|
||||
import crypto from "node:crypto";
|
||||
import Logging from "@proxnet/undead-logging";
|
||||
|
||||
const log = new Logging("UserRoute");
|
||||
|
||||
const config = Config.getConfig();
|
||||
|
||||
export const route = APIUtils.createRouter('/user');
|
||||
|
||||
type CreateUserBody = {
|
||||
username: string,
|
||||
password: string,
|
||||
recaptcha: string
|
||||
interface AuthRequestSec {
|
||||
timestamp: number,
|
||||
nonce: string,
|
||||
server_id: string
|
||||
}
|
||||
|
||||
type CreatedUserResponse = {
|
||||
uuid: string,
|
||||
backupcode: string
|
||||
interface AuthRequestRoot {
|
||||
client_id: string,
|
||||
message: AuthRequestSec,
|
||||
signature: string,
|
||||
pubkey: string
|
||||
}
|
||||
|
||||
const rateLimit = new APIUtils.RateLimiter(10, 1);
|
||||
const rateLimit = new APIUtils.RateLimiter(60, 1);
|
||||
|
||||
route.router.post('/create',
|
||||
route.router.post('/auth',
|
||||
|
||||
rateLimit.middle(),
|
||||
express.json(),
|
||||
APIUtils.checkBodyTypes<CreateUserBody>({ username: "test", password: "test", recaptcha: "test" }),
|
||||
APIUtils.checkBodyTypes<AuthRequestRoot>({
|
||||
client_id: "asdf",
|
||||
message: {
|
||||
timestamp: 0,
|
||||
nonce: "asdf",
|
||||
server_id: "asdf"
|
||||
},
|
||||
signature: "asdf",
|
||||
pubkey: "asdf"
|
||||
}),
|
||||
|
||||
async (rq, rs) => {
|
||||
const body = rq.body as CreateUserBody;
|
||||
async (rq: express.Request<NoBody, NoBody, AuthRequestRoot>, rs: express.Response) => {
|
||||
|
||||
const recaptchaStatus = await Recaptcha.siteVerify(body.recaptcha, getSrcIpDefault(rq));
|
||||
if (recaptchaStatus) {
|
||||
const userinit = await User.init({ username: body.username, password: body.password });
|
||||
if (userinit == null) {
|
||||
rs.statusCode = 400;
|
||||
rs.json(APIUtils.genericResponseFormat(true, "Username is already taken"));
|
||||
} else {
|
||||
function authFailed(msg: string) {
|
||||
rs.json(APIUtils.genericResponseFormat(true, msg));
|
||||
}
|
||||
|
||||
const res: CreatedUserResponse = {
|
||||
uuid: userinit.user.getUuid(),
|
||||
backupcode: userinit.backupcode
|
||||
}
|
||||
rs.json(APIUtils.genericResponseFormat(false, "User created successfully", res));
|
||||
if (rq.body.message.server_id !== config.public.serverId) {
|
||||
log.w(`Auth request failed (serverId mismatch), config error?\n given ID: '${rq.body.message.server_id}'\n our ID: '${config.public.serverId}'`);
|
||||
authFailed('Authentication request not intended for this server.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const verify = crypto.createVerify('SHA256');
|
||||
verify.update(JSON.stringify(rq.body.message));
|
||||
verify.end();
|
||||
|
||||
const publicKey = await crypto.subtle.importKey(
|
||||
"spki",
|
||||
(Uint8Array.from(atob(rq.body.pubkey), c => c.charCodeAt(0))).buffer,
|
||||
{ name: "ECDSA", namedCurve: "P-256" },
|
||||
false,
|
||||
["verify"]
|
||||
);
|
||||
const messageBytes = new TextEncoder().encode(JSON.stringify(rq.body.message));
|
||||
const signatureBytes = Uint8Array.from(atob(rq.body.signature), c => c.charCodeAt(0));
|
||||
const isValid = await crypto.subtle.verify(
|
||||
{ name: "ECDSA", hash: "SHA-256" },
|
||||
publicKey,
|
||||
signatureBytes.buffer,
|
||||
messageBytes
|
||||
);
|
||||
if (!isValid) {
|
||||
log.w(`Auth failed for clientId '${rq.body.client_id}'`);
|
||||
authFailed('Authentication request failed.');
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
log.d(`Error when verifying auth request: ${err}`);
|
||||
authFailed('Authentication request failed.');
|
||||
return;
|
||||
}
|
||||
else {
|
||||
rs.statusCode = 400;
|
||||
rs.json(APIUtils.genericResponseFormat(true, "ReCAPTCHA error"));
|
||||
|
||||
let user = new User(rq.body.client_id);
|
||||
if (!(await user.exists())) {
|
||||
const obj = await User.init({ client_id: rq.body.client_id, pubkey: rq.body.pubkey });
|
||||
if (obj == null) {
|
||||
rs.sendStatus(500);
|
||||
return;
|
||||
} else user = obj;
|
||||
}
|
||||
if (await user.hasNonce(rq.body.message.nonce)) {
|
||||
log.w(`Client '${rq.body.client_id}' has already used nonce. Replay attack?`);
|
||||
authFailed('Authentication request failed.');
|
||||
return;
|
||||
} else user.addNonce(rq.body.message.nonce);
|
||||
|
||||
const token = await user.getToken();
|
||||
rs.json({ token: token });
|
||||
|
||||
}
|
||||
|
||||
);
|
||||
Reference in New Issue
Block a user