-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Simplistic user-system with full MML-Support for characters A simplistic User-System allowing authorization, usernames and different MML-Characters with (permissioned) inventory for global and unique items. This is easily adapted to centralized databases and NFT-based blockchain-applications. Refer the User-System section in README.md for examples and details. Technically - Adds UserId which is hardcoded into the web-client - - A simplistic protocol with UserData message (for client-initated changes) and UserUpdate-message (for server-authorized distribution of user-details such as username, character, ..) - A simplistic inventory system supporting unique items, which can at most be used once within a 3d-web-experience. Code structured in a way s.t. it can easily be adapted to actual applications and authorization structures * User networking auth with chat * Allow reusing sessions to aid developer experience * Fix ExampleEnforcingUserAuthenticator --------- Co-authored-by: Thomas Bergmueller <[email protected]> Co-authored-by: Marco Gomez <[email protected]>
- Loading branch information
1 parent
8e179c2
commit a61f041
Showing
28 changed files
with
1,368 additions
and
322 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import crypto from "crypto"; | ||
|
||
import { UserIdentity } from "@mml-io/3d-web-user-networking"; | ||
import type { CharacterDescription, UserData } from "@mml-io/3d-web-user-networking"; | ||
import express from "express"; | ||
|
||
export type AuthUser = { | ||
// clientId is the connection identifier for the user - it is null before the client websocket is connected | ||
clientId: number | null; | ||
// userData is the user's presentation in the world (username and character description) | ||
userData?: UserData; | ||
// sessionToken is the token that is generated by this authenticator and the user uses to authenticate their websocket connection | ||
sessionToken: string; | ||
}; | ||
|
||
export type BasicUserAuthenticatorOptions = { | ||
devAllowUnrecognizedSessions: boolean; | ||
}; | ||
|
||
const defaultOptions: BasicUserAuthenticatorOptions = { | ||
devAllowUnrecognizedSessions: false, | ||
}; | ||
|
||
export class BasicUserAuthenticator { | ||
private usersByClientId = new Map<number, AuthUser>(); | ||
private userBySessionToken = new Map<string, AuthUser>(); | ||
|
||
constructor( | ||
private characterDescription: CharacterDescription, | ||
private options: BasicUserAuthenticatorOptions = defaultOptions, | ||
) {} | ||
|
||
public generateAuthorizedSessionToken(req: express.Request): string { | ||
const sessionToken = crypto.randomBytes(20).toString("hex"); | ||
const authUser: AuthUser = { | ||
clientId: null, | ||
sessionToken, | ||
}; | ||
|
||
this.userBySessionToken.set(sessionToken, authUser); | ||
return sessionToken; | ||
} | ||
|
||
public onClientConnect( | ||
clientId: number, | ||
sessionToken: string, | ||
userIdentityPresentedOnConnection?: UserIdentity, | ||
): UserData | null { | ||
console.log(`Client ID: ${clientId} joined with token`); | ||
let user = this.userBySessionToken.get(sessionToken); | ||
if (!user) { | ||
console.error(`Invalid initial user-update for clientId ${clientId}, unknown session`); | ||
|
||
if (this.options.devAllowUnrecognizedSessions) { | ||
console.warn(`Dev mode: allowing unrecognized session token`); | ||
user = { | ||
clientId: null, | ||
sessionToken, | ||
}; | ||
this.userBySessionToken.set(sessionToken, user); | ||
} | ||
return null; | ||
} | ||
|
||
if (user.clientId !== null) { | ||
console.error(`Session token already connected`); | ||
return null; | ||
} | ||
|
||
user.clientId = clientId; | ||
user.userData = { | ||
username: `User ${clientId}`, | ||
characterDescription: this.characterDescription, | ||
}; | ||
if (userIdentityPresentedOnConnection) { | ||
console.warn("Ignoring user-identity on initial connect"); | ||
} | ||
this.usersByClientId.set(clientId, user); | ||
return user.userData; | ||
} | ||
|
||
public getClientIdForSessionToken(sessionToken: string): { id: number } | null { | ||
const user = this.userBySessionToken.get(sessionToken); | ||
if (!user) { | ||
console.error("getClientIdForSessionToken - unknown session"); | ||
return null; | ||
} | ||
if (user.clientId === null) { | ||
console.error("getClientIdForSessionToken - client not connected"); | ||
return null; | ||
} | ||
return { id: user.clientId }; | ||
} | ||
|
||
public onClientUserIdentityUpdate(clientId: number, msg: UserIdentity): UserData | null { | ||
// This implementation does not allow updating user data after initial connect. | ||
|
||
// To allow updating user data after initial connect, return the UserData object that reflects the requested change. | ||
|
||
// Returning null will not update the user data. | ||
return null; | ||
} | ||
|
||
public onClientDisconnect(clientId: number) { | ||
console.log(`Remove user-session for ${clientId}`); | ||
// TODO - expire session token after a period of disconnection | ||
const userData = this.usersByClientId.get(clientId); | ||
if (userData) { | ||
userData.clientId = null; | ||
this.usersByClientId.delete(clientId); | ||
} | ||
} | ||
} |
Oops, something went wrong.