Further login process

* APIUtils addition: query validation
* Coach and Server accounts are now properly created if they do not exist
* Profiles now cannot be IDs 1 or 2 (reservedIds)
* Fixed profile username exists bug
* Added relationship manager
* Started relationship management
* DeviceClass and VRMovementMode enum defaults for reserved profiles
* Presence update simplification
* Progression fixes
* Relationship query and object fixes
* Base configuration is now rate limited
* Progression route no longer requires authentication, instead is rate limited
* Base relationships with reserved profiles (Coach and Server)
* DeviceClass required for login
* Get presence route
* Socket route no longer logs
* Socket target base finished
This commit is contained in:
2025-03-30 19:29:57 -04:00
parent 026f9c8bd8
commit 639e809a20
19 changed files with 270 additions and 81 deletions

View File

@@ -2,7 +2,6 @@ import { APIUtils } from "../apiutils.ts";
import { Config } from "../config.ts";
import { AuthType } from "../data/users.ts";
import { SocketHandoff } from "./handoff.ts";
import express from "express";
const config = Config.getConfig();
@@ -12,8 +11,6 @@ route.router.post('/hub/v1/negotiate',
APIUtils.Authentication,
APIUtils.AuthenticationType(AuthType.Game),
express.urlencoded({ extended: true }),
APIUtils.logBody,
(_rq, rs) => {
const handoff = new SocketHandoff();

View File

@@ -1,6 +1,18 @@
import { Profile } from "../data/profiles.ts";
import Logging from "@proxnet/undead-logging";
import { Message, MessageKind, SignalMessageType, SignalRMessage, SignalRMessageSchema, TargetResult, TargetResultFailure, TargetResultSuccess, TargetResultType } from "./types.ts";
import {
CompletionMessage,
Message,
MessageKind,
SignalMessageType,
SignalRMessage,
SignalRMessageSchema,
TargetResult,
TargetResultFailure,
TargetResultNotATarget,
TargetResultSuccess,
TargetResultType
} from "./types.ts";
import { SocketTarget } from "./targets/targetbase.ts";
import { PlayerSocketSubscriptionTarget } from "./targets/SubscribeToPlayers.ts";
@@ -26,22 +38,55 @@ export class SignalRSocketHandler {
}
async #dispatchTarget<T = unknown>(target: string, args: object[]): Promise<TargetResult> {
async #dispatchTarget<T = unknown>(target: string, args: unknown): Promise<TargetResult> {
const targetExec = this.#Targets.get(target);
if (!targetExec) return { type: TargetResultType.Failure } as TargetResultFailure;
else return { type: TargetResultType.Success, data: await targetExec.exec(args) } as TargetResultSuccess<T>;
if (!targetExec) return { type: TargetResultType.NotATarget } as TargetResultNotATarget;
else {
try {
return { type: TargetResultType.Success, data: await targetExec.exec(args) } as TargetResultSuccess<T>;
} catch (err) {
this.#log.w(`Target '${target}' function error: ${err}`);
if (err instanceof Error) return { type: TargetResultType.Failure, err: err } as TargetResultFailure;
else return { type: TargetResultType.Failure, err: `${err}` } as TargetResultFailure;
}
}
}
#onMessage(message: Message) {
async #onMessage(message: Message) {
if (message.kind == MessageKind.Protocol) {
this.sendRaw({});
return;
} else {
this.#log.d(`CLIENT MESSAGE\n Type: ${message.data.type} (${SignalMessageType[message.data.type]})\n ${JSON.stringify(message.data)}`);
if (message.data.type == SignalMessageType.Invocation && message.data.invocationId) { // don't send completion messages for nonblocking invocations
const res = await this.#dispatchTarget(message.data.target, message.data.arguments[0]); // rec room only uses the first index
if (res.type == TargetResultType.Success) {
const signalRes: CompletionMessage = {
type: SignalMessageType.Completion,
invocationId: message.data.invocationId,
result: JSON.stringify(res.data)
}
this.sendRaw(signalRes);
} else if (res.type == TargetResultType.Failure) {
const signalRes: CompletionMessage = {
type: SignalMessageType.Completion,
invocationId: message.data.invocationId,
error: res.err instanceof Error ? res.err.message : res.err
}
this.sendRaw(signalRes);
} else {
const signalRes: CompletionMessage = {
type: SignalMessageType.Completion,
invocationId: message.data.invocationId,
error: "Target not found"
}
this.sendRaw(signalRes);
}
}
}
}
async #init() {
#init() {
this.#log.source += this.#profile.getId().toString();
this.#log.i(`Created hub socket`);
@@ -82,6 +127,9 @@ export class SignalRSocketHandler {
sendRaw(data: object) {
this.#socket.send(`${JSON.stringify(data)}\u001e`);
// todo sometime: make this less confusing
const type = `Type: ${JSON.stringify(data) == '{}' ? 'Protocol Message' : `${(data as SignalRMessage).type} (${SignalMessageType[(data as SignalRMessage).type]})`}`;
this.#log.d(`SERVER MESSAGE\n ${type}\n ${JSON.stringify(data as SignalRMessage)}`);
}
}

View File

@@ -1,5 +1,10 @@
import { z } from "zod";
import { SocketTarget } from "./targetbase.ts";
const ArgumentSchema = z.object({
PlayerIds: z.array(z.number())
});
export class PlayerSocketSubscriptionTarget extends SocketTarget {
subscriptions: number[] = [];
@@ -9,8 +14,12 @@ export class PlayerSocketSubscriptionTarget extends SocketTarget {
}
// deno-lint-ignore require-await
override async exec(_args: (object | string | number | boolean)[]) {
return;
override async exec(args: unknown) {
const parsed = ArgumentSchema.safeParse(args);
if (parsed.success) {
this.setSubscriptions(parsed.data.PlayerIds);
return;
} else throw new Error("Invalid arguments");
}
}

View File

@@ -13,7 +13,7 @@ export class SocketTarget {
}
// deno-lint-ignore require-await
async exec(_args: (object | string | number | boolean)[]) {
async exec(_args: unknown) {
throw new Error("Execution for this target is not set.");
}

View File

@@ -4,14 +4,14 @@ export enum MessageKind {
Protocol,
Data
}
interface MessageBase {
export interface MessageBase {
kind: MessageKind
}
interface DataMessage extends MessageBase {
export interface DataMessage extends MessageBase {
kind: MessageKind.Data,
data: SignalRMessage
}
interface ProtocolMessage extends MessageBase {
export interface ProtocolMessage extends MessageBase {
kind: MessageKind.Protocol
}
export type Message = ProtocolMessage | DataMessage;
@@ -34,68 +34,68 @@ export enum SignalMessageType {
Close
}
interface BaseMessage {
export interface BaseMessage {
type: SignalMessageType;
}
interface InvocationMessage extends BaseMessage {
export interface InvocationMessage extends BaseMessage {
type: SignalMessageType.Invocation;
target: string;
arguments: unknown[];
invocationId?: string;
}
interface StreamItemMessage extends BaseMessage {
export interface StreamItemMessage extends BaseMessage {
type: SignalMessageType.StreamItem;
invocationId: string;
item: unknown;
}
interface CompletionMessage extends BaseMessage {
export interface CompletionMessage extends BaseMessage {
type: SignalMessageType.Completion;
invocationId: string;
result?: unknown;
error?: string;
}
interface PingMessage extends BaseMessage {
export interface PingMessage extends BaseMessage {
type: SignalMessageType.Ping;
}
interface CloseMessage extends BaseMessage {
export interface CloseMessage extends BaseMessage {
type: SignalMessageType.Close;
error?: string;
}
const BaseMessageSchema = z.object({
export const BaseMessageSchema = z.object({
type: z.nativeEnum(SignalMessageType),
});
const InvocationMessageSchema = BaseMessageSchema.extend({
export const InvocationMessageSchema = BaseMessageSchema.extend({
type: z.literal(SignalMessageType.Invocation),
target: z.string(),
arguments: z.array(z.unknown()),
invocationId: z.string().optional(),
});
const StreamItemMessageSchema = BaseMessageSchema.extend({
export const StreamItemMessageSchema = BaseMessageSchema.extend({
type: z.literal(SignalMessageType.StreamItem),
invocationId: z.string(),
item: z.unknown(),
});
const CompletionMessageSchema = BaseMessageSchema.extend({
export const CompletionMessageSchema = BaseMessageSchema.extend({
type: z.literal(SignalMessageType.Completion),
invocationId: z.string(),
result: z.unknown().optional(),
error: z.string().optional(),
});
const PingMessageSchema = BaseMessageSchema.extend({
export const PingMessageSchema = BaseMessageSchema.extend({
type: z.literal(SignalMessageType.Ping),
});
const CloseMessageSchema = BaseMessageSchema.extend({
export const CloseMessageSchema = BaseMessageSchema.extend({
type: z.literal(SignalMessageType.Close),
error: z.string().optional(),
});
@@ -113,7 +113,7 @@ export enum TargetResultType {
Failure,
NotATarget
}
interface TargetResultBase {
export interface TargetResultBase {
type: TargetResultType
}
export interface TargetResultSuccess<T = unknown> extends TargetResultBase {
@@ -122,6 +122,7 @@ export interface TargetResultSuccess<T = unknown> extends TargetResultBase {
}
export interface TargetResultFailure extends TargetResultBase {
type: TargetResultType.Failure
err: string | Error
}
export interface TargetResultNotATarget extends TargetResultBase {
type: TargetResultType.NotATarget