Active authentication Oauth (#1377)
* Active authentication Oauth - Google authentication - GitHub authentication - Linkedin authentication Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com> * Finish connexion et get user info connexion Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com> * Fix lint error Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com> * Change the expires token for 30 days Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com> * Update connexion stratgey - Set last room when it will be created and not when connexion is openned - Add '/login' end point permit to logout and open iframe to log user - Add logout feature permit to logout in front Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com> * Implement logout and revoke token with hydra Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com> * Fix pull develop conflict Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com> * Profile url (#1399) * Create function that permit to get profile URL Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com> * Continue profil user Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com> * Add menu and logout button Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com> * Update last room use Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com> * Profile callback permit to get url profile setting from admin Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com> * Finish profile show Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com> * Delete profileUrl will be not use today Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com> * Correct lint Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com> * Update size of iframe Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com> * Delete console log Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com> * Update feedback ARP Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>
This commit is contained in:
parent
a0d863569b
commit
d2b8d7dc04
0
front/dist/resources/html/gameMenu.html
vendored
Normal file
0
front/dist/resources/html/gameMenu.html
vendored
Normal file
@ -1,15 +1,17 @@
|
||||
<script lang="typescript">
|
||||
import {gameManager} from "../../Phaser/Game/GameManager";
|
||||
import {SelectCompanionScene, SelectCompanionSceneName} from "../../Phaser/Login/SelectCompanionScene";
|
||||
import {menuIconVisiblilityStore, menuVisiblilityStore} from "../../Stores/MenuStore";
|
||||
import {menuIconVisiblilityStore, menuVisiblilityStore, userIsConnected} from "../../Stores/MenuStore";
|
||||
import {selectCompanionSceneVisibleStore} from "../../Stores/SelectCompanionStore";
|
||||
import {LoginScene, LoginSceneName} from "../../Phaser/Login/LoginScene";
|
||||
import {loginSceneVisibleStore} from "../../Stores/LoginSceneStore";
|
||||
import {selectCharacterSceneVisibleStore} from "../../Stores/SelectCharacterStore";
|
||||
import {SelectCharacterScene, SelectCharacterSceneName} from "../../Phaser/Login/SelectCharacterScene";
|
||||
import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||
import {PROFILE_URL} from "../../Enum/EnvironmentVariable";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {EnableCameraScene, EnableCameraSceneName} from "../../Phaser/Login/EnableCameraScene";
|
||||
import {enableCameraSceneVisibilityStore} from "../../Stores/MediaStore";
|
||||
//import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||
|
||||
|
||||
function disableMenuStores(){
|
||||
@ -35,6 +37,16 @@
|
||||
gameManager.leaveGame(SelectCharacterSceneName,new SelectCharacterScene());
|
||||
}
|
||||
|
||||
function logOut(){
|
||||
disableMenuStores();
|
||||
loginSceneVisibleStore.set(true);
|
||||
connectionManager.logout();
|
||||
}
|
||||
|
||||
function getProfileUrl(){
|
||||
return PROFILE_URL + `?token=${localUserStore.getAuthToken()}`;
|
||||
}
|
||||
|
||||
function openEnableCameraScene(){
|
||||
disableMenuStores();
|
||||
enableCameraSceneVisibilityStore.showEnableCameraScene();
|
||||
@ -45,17 +57,26 @@
|
||||
/*function clickLogin() {
|
||||
connectionManager.loadOpenIDScreen();
|
||||
}*/
|
||||
|
||||
</script>
|
||||
|
||||
<div class="customize-main">
|
||||
{#if $userIsConnected}
|
||||
<section>
|
||||
{#if PROFILE_URL != undefined}
|
||||
<iframe title="profile" src="{getProfileUrl()}"></iframe>
|
||||
{/if}
|
||||
</section>
|
||||
<section>
|
||||
<button type="button" class="nes-btn" on:click|preventDefault={logOut}>Log out</button>
|
||||
</section>
|
||||
{:else}
|
||||
<section>
|
||||
<a type="button" class="nes-btn" href="/login">Sing in</a>
|
||||
</section>
|
||||
{/if}
|
||||
<section>
|
||||
<button type="button" class="nes-btn" on:click|preventDefault={openEditNameScene}>Edit Name</button>
|
||||
</section>
|
||||
<section>
|
||||
<button type="button" class="nes-btn is-rounded" on:click|preventDefault={openEditSkinScene}>Edit Skin</button>
|
||||
</section>
|
||||
<section>
|
||||
<button type="button" class="nes-btn" on:click|preventDefault={openEditCompanionScene}>Edit Companion</button>
|
||||
</section>
|
||||
<section>
|
||||
@ -74,6 +95,12 @@
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
iframe{
|
||||
width: 100%;
|
||||
height: 50vh;
|
||||
border: none;
|
||||
}
|
||||
|
||||
button {
|
||||
height: 50px;
|
||||
width: 250px;
|
||||
|
@ -7,6 +7,8 @@ import { localUserStore } from "./LocalUserStore";
|
||||
import { CharacterTexture, LocalUser } from "./LocalUser";
|
||||
import { Room } from "./Room";
|
||||
import { _ServiceWorker } from "../Network/ServiceWorker";
|
||||
import { loginSceneVisibleIframeStore } from "../Stores/LoginSceneStore";
|
||||
import { userIsConnected } from "../Stores/MenuStore";
|
||||
|
||||
class ConnectionManager {
|
||||
private localUser!: LocalUser;
|
||||
@ -15,6 +17,7 @@ class ConnectionManager {
|
||||
private reconnectingTimeout: NodeJS.Timeout | null = null;
|
||||
private _unloading: boolean = false;
|
||||
private authToken: string | null = null;
|
||||
private _currentRoom: Room | null = null;
|
||||
|
||||
private serviceWorker?: _ServiceWorker;
|
||||
|
||||
@ -30,28 +33,39 @@ class ConnectionManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Promise<void>
|
||||
* TODO fix me to be move in game manager
|
||||
*/
|
||||
public loadOpenIDScreen(): Promise<void> {
|
||||
public loadOpenIDScreen() {
|
||||
const state = localUserStore.generateState();
|
||||
const nonce = localUserStore.generateNonce();
|
||||
localUserStore.setAuthToken(null);
|
||||
|
||||
//TODO refactor this and don't realise previous call
|
||||
return Axios.get(`http://${PUSHER_URL}/login-screen?state=${state}&nonce=${nonce}`)
|
||||
.then(() => {
|
||||
window.location.assign(`http://${PUSHER_URL}/login-screen?state=${state}&nonce=${nonce}`);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err, "We don't have URL to regenerate authentication user");
|
||||
//TODO show modal login
|
||||
window.location.reload();
|
||||
});
|
||||
//TODO fix me to redirect this URL by pusher
|
||||
if (!this._currentRoom || !this._currentRoom.iframeAuthentication) {
|
||||
loginSceneVisibleIframeStore.set(false);
|
||||
return null;
|
||||
}
|
||||
const redirectUrl = `${this._currentRoom.iframeAuthentication}?state=${state}&nonce=${nonce}`;
|
||||
window.location.assign(redirectUrl);
|
||||
return redirectUrl;
|
||||
}
|
||||
|
||||
public logout() {
|
||||
/**
|
||||
* Logout
|
||||
*/
|
||||
public async logout() {
|
||||
//user logout, set connected store for menu at false
|
||||
userIsConnected.set(false);
|
||||
|
||||
//Logout user in pusher and hydra
|
||||
const token = localUserStore.getAuthToken();
|
||||
const { authToken } = await Axios.get(`${PUSHER_URL}/logout-callback`, { params: { token } }).then(
|
||||
(res) => res.data
|
||||
);
|
||||
localUserStore.setAuthToken(null);
|
||||
window.location.reload();
|
||||
|
||||
//Go on login page can permit to clear token and start authentication process
|
||||
window.location.assign("/login");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -60,8 +74,13 @@ class ConnectionManager {
|
||||
public async initGameConnexion(): Promise<Room> {
|
||||
const connexionType = urlManager.getGameConnexionType();
|
||||
this.connexionType = connexionType;
|
||||
let room: Room | null = null;
|
||||
if (connexionType === GameConnexionTypes.jwt) {
|
||||
this._currentRoom = null;
|
||||
if (connexionType === GameConnexionTypes.login) {
|
||||
//TODO clear all cash and redirect on login scene (iframe)
|
||||
localUserStore.setAuthToken(null);
|
||||
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
|
||||
urlManager.pushRoomIdToUrl(this._currentRoom);
|
||||
} else if (connexionType === GameConnexionTypes.jwt) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const code = urlParams.get("code");
|
||||
const state = urlParams.get("state");
|
||||
@ -71,14 +90,15 @@ class ConnectionManager {
|
||||
if (!code) {
|
||||
throw "No Auth code provided";
|
||||
}
|
||||
const nonce = localUserStore.getNonce();
|
||||
const { authToken } = await Axios.get(`${PUSHER_URL}/login-callback`, { params: { code, nonce } }).then(
|
||||
(res) => res.data
|
||||
);
|
||||
localUserStore.setAuthToken(authToken);
|
||||
this.authToken = authToken;
|
||||
room = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
|
||||
urlManager.pushRoomIdToUrl(room);
|
||||
localUserStore.setCode(code);
|
||||
try {
|
||||
await this.checkAuthUserConnexion();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
this.loadOpenIDScreen();
|
||||
}
|
||||
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
|
||||
urlManager.pushRoomIdToUrl(this._currentRoom);
|
||||
} else if (connexionType === GameConnexionTypes.register) {
|
||||
//@deprecated
|
||||
const organizationMemberToken = urlManager.getOrganizationToken();
|
||||
@ -92,7 +112,7 @@ class ConnectionManager {
|
||||
|
||||
const roomUrl = data.roomUrl;
|
||||
|
||||
room = await Room.createRoom(
|
||||
this._currentRoom = await Room.createRoom(
|
||||
new URL(
|
||||
window.location.protocol +
|
||||
"//" +
|
||||
@ -102,7 +122,7 @@ class ConnectionManager {
|
||||
window.location.hash
|
||||
)
|
||||
);
|
||||
urlManager.pushRoomIdToUrl(room);
|
||||
urlManager.pushRoomIdToUrl(this._currentRoom);
|
||||
} else if (
|
||||
connexionType === GameConnexionTypes.organization ||
|
||||
connexionType === GameConnexionTypes.anonymous ||
|
||||
@ -112,12 +132,18 @@ class ConnectionManager {
|
||||
//todo: add here some kind of warning if authToken has expired.
|
||||
if (!this.authToken) {
|
||||
await this.anonymousLogin();
|
||||
} else {
|
||||
try {
|
||||
await this.checkAuthUserConnexion();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
this.localUser = localUserStore.getLocalUser() as LocalUser; //if authToken exist in localStorage then localUser cannot be null
|
||||
|
||||
let roomPath: string;
|
||||
if (connexionType === GameConnexionTypes.empty) {
|
||||
roomPath = window.location.protocol + "//" + window.location.host + START_ROOM_URL;
|
||||
roomPath = localUserStore.getLastRoomUrl();
|
||||
//get last room path from cache api
|
||||
try {
|
||||
const lastRoomUrl = await localUserStore.getLastRoomUrlCacheApi();
|
||||
@ -138,13 +164,13 @@ class ConnectionManager {
|
||||
}
|
||||
|
||||
//get detail map for anonymous login and set texture in local storage
|
||||
room = await Room.createRoom(new URL(roomPath));
|
||||
if (room.textures != undefined && room.textures.length > 0) {
|
||||
this._currentRoom = await Room.createRoom(new URL(roomPath));
|
||||
if (this._currentRoom.textures != undefined && this._currentRoom.textures.length > 0) {
|
||||
//check if texture was changed
|
||||
if (this.localUser.textures.length === 0) {
|
||||
this.localUser.textures = room.textures;
|
||||
this.localUser.textures = this._currentRoom.textures;
|
||||
} else {
|
||||
room.textures.forEach((newTexture) => {
|
||||
this._currentRoom.textures.forEach((newTexture) => {
|
||||
const alreadyExistTexture = this.localUser.textures.find((c) => newTexture.id === c.id);
|
||||
if (this.localUser.textures.findIndex((c) => newTexture.id === c.id) !== -1) {
|
||||
return;
|
||||
@ -155,12 +181,12 @@ class ConnectionManager {
|
||||
localUserStore.saveUser(this.localUser);
|
||||
}
|
||||
}
|
||||
if (room == undefined) {
|
||||
if (this._currentRoom == undefined) {
|
||||
return Promise.reject(new Error("Invalid URL"));
|
||||
}
|
||||
|
||||
this.serviceWorker = new _ServiceWorker();
|
||||
return Promise.resolve(room);
|
||||
return Promise.resolve(this._currentRoom);
|
||||
}
|
||||
|
||||
public async anonymousLogin(isBenchmark: boolean = false): Promise<void> {
|
||||
@ -215,9 +241,6 @@ class ConnectionManager {
|
||||
});
|
||||
|
||||
connection.onConnect((connect: OnConnectInterface) => {
|
||||
//save last room url connected
|
||||
localUserStore.setLastRoomUrl(roomUrl);
|
||||
|
||||
resolve(connect);
|
||||
});
|
||||
}).catch((err) => {
|
||||
@ -237,6 +260,34 @@ class ConnectionManager {
|
||||
get getConnexionType() {
|
||||
return this.connexionType;
|
||||
}
|
||||
|
||||
async checkAuthUserConnexion() {
|
||||
//set connected store for menu at false
|
||||
userIsConnected.set(false);
|
||||
|
||||
const state = localUserStore.getState();
|
||||
const code = localUserStore.getCode();
|
||||
if (!state || !localUserStore.verifyState(state)) {
|
||||
throw "Could not validate state!";
|
||||
}
|
||||
if (!code) {
|
||||
throw "No Auth code provided";
|
||||
}
|
||||
const nonce = localUserStore.getNonce();
|
||||
const token = localUserStore.getAuthToken();
|
||||
const { authToken } = await Axios.get(`${PUSHER_URL}/login-callback`, { params: { code, nonce, token } }).then(
|
||||
(res) => res.data
|
||||
);
|
||||
localUserStore.setAuthToken(authToken);
|
||||
this.authToken = authToken;
|
||||
|
||||
//user connected, set connected store for menu at true
|
||||
userIsConnected.set(true);
|
||||
}
|
||||
|
||||
get currentRoom() {
|
||||
return this._currentRoom;
|
||||
}
|
||||
}
|
||||
|
||||
export const connectionManager = new ConnectionManager();
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { areCharacterLayersValid, isUserNameValid, LocalUser } from "./LocalUser";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { START_ROOM_URL } from "../Enum/EnvironmentVariable";
|
||||
|
||||
const playerNameKey = "playerName";
|
||||
const selectedPlayerKey = "selectedPlayer";
|
||||
@ -17,6 +18,7 @@ const authToken = "authToken";
|
||||
const state = "state";
|
||||
const nonce = "nonce";
|
||||
const notification = "notificationPermission";
|
||||
const code = "code";
|
||||
const cameraSetup = "cameraSetup";
|
||||
|
||||
const cacheAPIIndex = "workavdenture-cache";
|
||||
@ -126,7 +128,9 @@ class LocalUserStore {
|
||||
});
|
||||
}
|
||||
getLastRoomUrl(): string {
|
||||
return localStorage.getItem(lastRoomUrl) ?? "";
|
||||
return (
|
||||
localStorage.getItem(lastRoomUrl) ?? window.location.protocol + "//" + window.location.host + START_ROOM_URL
|
||||
);
|
||||
}
|
||||
getLastRoomUrlCacheApi(): Promise<string | undefined> {
|
||||
return caches.open(cacheAPIIndex).then((cache) => {
|
||||
@ -161,19 +165,24 @@ class LocalUserStore {
|
||||
|
||||
verifyState(value: string): boolean {
|
||||
const oldValue = localStorage.getItem(state);
|
||||
localStorage.removeItem(state);
|
||||
return oldValue === value;
|
||||
}
|
||||
getState(): string | null {
|
||||
return localStorage.getItem(state);
|
||||
}
|
||||
generateNonce(): string {
|
||||
const newNonce = uuidv4();
|
||||
localStorage.setItem(nonce, newNonce);
|
||||
return newNonce;
|
||||
}
|
||||
|
||||
getNonce(): string | null {
|
||||
const oldValue = localStorage.getItem(nonce);
|
||||
localStorage.removeItem(nonce);
|
||||
return oldValue;
|
||||
return localStorage.getItem(nonce);
|
||||
}
|
||||
setCode(value: string): void {
|
||||
localStorage.setItem(code, value);
|
||||
}
|
||||
getCode(): string | null {
|
||||
return localStorage.getItem(code);
|
||||
}
|
||||
|
||||
setCameraSetup(cameraId: string) {
|
||||
|
@ -14,6 +14,8 @@ export interface RoomRedirect {
|
||||
export class Room {
|
||||
public readonly id: string;
|
||||
public readonly isPublic: boolean;
|
||||
private _authenticationMandatory: boolean = false;
|
||||
private _iframeAuthentication?: string;
|
||||
private _mapUrl: string | undefined;
|
||||
private _textures: CharacterTexture[] | undefined;
|
||||
private instance: string | undefined;
|
||||
@ -101,6 +103,8 @@ export class Room {
|
||||
console.log("Map ", this.id, " resolves to URL ", data.mapUrl);
|
||||
this._mapUrl = data.mapUrl;
|
||||
this._textures = data.textures;
|
||||
this._authenticationMandatory = data.authenticationMandatory || false;
|
||||
this._iframeAuthentication = data.iframeAuthentication;
|
||||
return new MapDetail(data.mapUrl, data.textures);
|
||||
}
|
||||
|
||||
@ -186,4 +190,12 @@ export class Room {
|
||||
}
|
||||
return this._mapUrl;
|
||||
}
|
||||
|
||||
get authenticationMandatory(): boolean {
|
||||
return this._authenticationMandatory;
|
||||
}
|
||||
|
||||
get iframeAuthentication(): string | undefined {
|
||||
return this._iframeAuthentication;
|
||||
}
|
||||
}
|
||||
|
@ -78,6 +78,11 @@ export class RoomConnection implements RoomConnection {
|
||||
*
|
||||
* @param token A JWT token containing the email of the user
|
||||
* @param roomUrl The URL of the room in the form "https://example.com/_/[instance]/[map_url]" or "https://example.com/@/[org]/[event]/[map]"
|
||||
* @param name
|
||||
* @param characterLayers
|
||||
* @param position
|
||||
* @param viewport
|
||||
* @param companion
|
||||
*/
|
||||
public constructor(
|
||||
token: string | null,
|
||||
@ -218,7 +223,7 @@ export class RoomConnection implements RoomConnection {
|
||||
worldFullMessageStream.onMessage();
|
||||
this.closed = true;
|
||||
} else if (message.hasTokenexpiredmessage()) {
|
||||
connectionManager.loadOpenIDScreen();
|
||||
connectionManager.logout();
|
||||
this.closed = true; //technically, this isn't needed since loadOpenIDScreen() will do window.location.assign() but I prefer to leave it for consistency
|
||||
} else if (message.hasWorldconnexionmessage()) {
|
||||
worldFullMessageStream.onMessage(message.getWorldconnexionmessage()?.getMessage());
|
||||
|
@ -19,6 +19,7 @@ export const MAX_PER_GROUP = parseInt(process.env.MAX_PER_GROUP || "4");
|
||||
export const DISPLAY_TERMS_OF_USE = process.env.DISPLAY_TERMS_OF_USE == "true";
|
||||
export const NODE_ENV = process.env.NODE_ENV || "development";
|
||||
export const CONTACT_URL = process.env.CONTACT_URL || undefined;
|
||||
export const PROFILE_URL = process.env.PROFILE_URL || undefined;
|
||||
|
||||
export const isMobile = (): boolean => window.innerWidth <= 800 || window.innerHeight <= 600;
|
||||
|
||||
|
@ -35,7 +35,9 @@ export class GameManager {
|
||||
this.startRoom = await connectionManager.initGameConnexion();
|
||||
this.loadMap(this.startRoom);
|
||||
|
||||
if (!this.playerName) {
|
||||
//If player name was not set show login scene with player name
|
||||
//If Room si not public and Auth was not set, show login scene to authenticate user (OpenID - SSO - Anonymous)
|
||||
if (!this.playerName || (this.startRoom.authenticationMandatory && !localUserStore.getAuthToken())) {
|
||||
return LoginSceneName;
|
||||
} else if (!this.characterLayers || !this.characterLayers.length) {
|
||||
return SelectCharacterSceneName;
|
||||
@ -143,6 +145,10 @@ export class GameManager {
|
||||
if (this.currentGameSceneName === null) throw "No current scene id set!";
|
||||
return this.scenePlugin.get(this.currentGameSceneName) as GameScene;
|
||||
}
|
||||
|
||||
public get currentStartedRoom() {
|
||||
return this.startRoom;
|
||||
}
|
||||
}
|
||||
|
||||
export const gameManager = new GameManager();
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { gameManager } from "../Game/GameManager";
|
||||
import { SelectCharacterSceneName } from "./SelectCharacterScene";
|
||||
import { ResizableScene } from "./ResizableScene";
|
||||
import { loginSceneVisibleStore } from "../../Stores/LoginSceneStore";
|
||||
import { loginSceneVisibleIframeStore, loginSceneVisibleStore } from "../../Stores/LoginSceneStore";
|
||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import { connectionManager } from "../../Connexion/ConnectionManager";
|
||||
import { gameManager } from "../Game/GameManager";
|
||||
|
||||
export const LoginSceneName = "LoginScene";
|
||||
|
||||
@ -18,6 +20,16 @@ export class LoginScene extends ResizableScene {
|
||||
preload() {}
|
||||
|
||||
create() {
|
||||
loginSceneVisibleIframeStore.set(false);
|
||||
//If authentication is mandatory, push authentication iframe
|
||||
if (
|
||||
localUserStore.getAuthToken() == undefined &&
|
||||
gameManager.currentStartedRoom &&
|
||||
gameManager.currentStartedRoom?.authenticationMandatory
|
||||
) {
|
||||
connectionManager.loadOpenIDScreen();
|
||||
loginSceneVisibleIframeStore.set(true);
|
||||
}
|
||||
loginSceneVisibleStore.set(true);
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export const loginSceneVisibleStore = writable(false);
|
||||
export const loginSceneVisibleIframeStore = writable(false);
|
||||
|
@ -6,6 +6,8 @@ import { CONTACT_URL } from "../Enum/EnvironmentVariable";
|
||||
export const menuIconVisiblilityStore = writable(false);
|
||||
export const menuVisiblilityStore = writable(false);
|
||||
export const menuInputFocusStore = writable(false);
|
||||
export const loginUrlStore = writable(false);
|
||||
export const userIsConnected = writable(false);
|
||||
|
||||
let warningContainerTimeout: Timeout | null = null;
|
||||
function createWarningContainerStore() {
|
||||
|
@ -2,6 +2,7 @@ import { writable } from "svelte/store";
|
||||
import type { PlayerInterface } from "../Phaser/Game/PlayerInterface";
|
||||
import type { RoomConnection } from "../Connexion/RoomConnection";
|
||||
import { getRandomColor } from "../WebRtc/ColorGenerator";
|
||||
import { localUserStore } from "../Connexion/LocalUserStore";
|
||||
|
||||
let idCount = 0;
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import type { Room } from "../Connexion/Room";
|
||||
import { localUserStore } from "../Connexion/LocalUserStore";
|
||||
|
||||
export enum GameConnexionTypes {
|
||||
anonymous = 1,
|
||||
@ -7,13 +8,16 @@ export enum GameConnexionTypes {
|
||||
empty,
|
||||
unknown,
|
||||
jwt,
|
||||
login,
|
||||
}
|
||||
|
||||
//this class is responsible with analysing and editing the game's url
|
||||
class UrlManager {
|
||||
public getGameConnexionType(): GameConnexionTypes {
|
||||
const url = window.location.pathname.toString();
|
||||
if (url === "/jwt") {
|
||||
if (url === "/login") {
|
||||
return GameConnexionTypes.login;
|
||||
} else if (url === "/jwt") {
|
||||
return GameConnexionTypes.jwt;
|
||||
} else if (url.includes("_/")) {
|
||||
return GameConnexionTypes.anonymous;
|
||||
@ -35,6 +39,8 @@ class UrlManager {
|
||||
|
||||
public pushRoomIdToUrl(room: Room): void {
|
||||
if (window.location.pathname === room.id) return;
|
||||
//Set last room visited! (connected or nor, must to be saved in localstorage and cache API)
|
||||
localUserStore.setLastRoomUrl(room.key);
|
||||
const hash = window.location.hash;
|
||||
const search = room.search.toString();
|
||||
history.pushState({}, "WorkAdventure", room.id + (search ? "?" + search : "") + hash);
|
||||
|
@ -7,6 +7,7 @@ import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
||||
import sveltePreprocess from "svelte-preprocess";
|
||||
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
|
||||
import NodePolyfillPlugin from "node-polyfill-webpack-plugin";
|
||||
import { PROFILE_URL } from "./src/Enum/EnvironmentVariable";
|
||||
|
||||
const mode = process.env.NODE_ENV ?? "development";
|
||||
const buildNpmTypingsForApi = !!process.env.BUILD_TYPINGS;
|
||||
@ -191,6 +192,7 @@ module.exports = {
|
||||
UPLOADER_URL: null,
|
||||
ADMIN_URL: undefined,
|
||||
CONTACT_URL: null,
|
||||
PROFILE_URL: null,
|
||||
DEBUG_MODE: null,
|
||||
STUN_SERVER: null,
|
||||
TURN_SERVER: null,
|
||||
|
@ -2,7 +2,7 @@ import { v4 } from "uuid";
|
||||
import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
|
||||
import { BaseController } from "./BaseController";
|
||||
import { adminApi } from "../Services/AdminApi";
|
||||
import { jwtTokenManager } from "../Services/JWTTokenManager";
|
||||
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
||||
import { parse } from "query-string";
|
||||
import { openIDClient } from "../Services/OpenIDClient";
|
||||
|
||||
@ -17,6 +17,7 @@ export class AuthenticateController extends BaseController {
|
||||
this.openIDCallback();
|
||||
this.register();
|
||||
this.anonymLogin();
|
||||
this.profileCallback();
|
||||
}
|
||||
|
||||
openIDLogin() {
|
||||
@ -48,14 +49,31 @@ export class AuthenticateController extends BaseController {
|
||||
res.onAborted(() => {
|
||||
console.warn("/message request was aborted");
|
||||
});
|
||||
const { code, nonce } = parse(req.getQuery());
|
||||
const { code, nonce, token } = parse(req.getQuery());
|
||||
try {
|
||||
//verify connected by token
|
||||
if (token != undefined) {
|
||||
try {
|
||||
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
||||
if (authTokenData.hydraAccessToken == undefined) {
|
||||
throw Error("Token cannot to be check on Hydra");
|
||||
}
|
||||
await openIDClient.checkTokenAuth(authTokenData.hydraAccessToken);
|
||||
res.writeStatus("200");
|
||||
this.addCorsHeaders(res);
|
||||
return res.end(JSON.stringify({ authToken: token }));
|
||||
} catch (err) {
|
||||
console.info("User was not connected", err);
|
||||
}
|
||||
}
|
||||
|
||||
//user have not token created, check data on hydra and create token
|
||||
const userInfo = await openIDClient.getUserInfo(code as string, nonce as string);
|
||||
const email = userInfo.email || userInfo.sub;
|
||||
if (!email) {
|
||||
throw new Error("No email in the response");
|
||||
}
|
||||
const authToken = jwtTokenManager.createAuthToken(email);
|
||||
const authToken = jwtTokenManager.createAuthToken(email, userInfo.access_token);
|
||||
res.writeStatus("200");
|
||||
this.addCorsHeaders(res);
|
||||
return res.end(JSON.stringify({ authToken }));
|
||||
@ -63,6 +81,30 @@ export class AuthenticateController extends BaseController {
|
||||
return this.errorToResponse(e, res);
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
this.App.get("/logout-callback", async (res: HttpResponse, req: HttpRequest) => {
|
||||
res.onAborted(() => {
|
||||
console.warn("/message request was aborted");
|
||||
});
|
||||
|
||||
const { token } = parse(req.getQuery());
|
||||
|
||||
try {
|
||||
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
||||
if (authTokenData.hydraAccessToken == undefined) {
|
||||
throw Error("Token cannot to be logout on Hydra");
|
||||
}
|
||||
await openIDClient.logoutUser(authTokenData.hydraAccessToken);
|
||||
} catch (error) {
|
||||
console.error("openIDCallback => logout-callback", error);
|
||||
} finally {
|
||||
res.writeStatus("200");
|
||||
this.addCorsHeaders(res);
|
||||
// eslint-disable-next-line no-unsafe-finally
|
||||
return res.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Try to login with an admin token
|
||||
@ -136,4 +178,39 @@ export class AuthenticateController extends BaseController {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
profileCallback() {
|
||||
//eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
this.App.get("/profile-callback", async (res: HttpResponse, req: HttpRequest) => {
|
||||
res.onAborted(() => {
|
||||
console.warn("/message request was aborted");
|
||||
});
|
||||
const { userIdentify, token } = parse(req.getQuery());
|
||||
try {
|
||||
//verify connected by token
|
||||
if (token != undefined) {
|
||||
try {
|
||||
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
||||
if (authTokenData.hydraAccessToken == undefined) {
|
||||
throw Error("Token cannot to be check on Hydra");
|
||||
}
|
||||
await openIDClient.checkTokenAuth(authTokenData.hydraAccessToken);
|
||||
|
||||
//get login profile
|
||||
res.writeStatus("302");
|
||||
res.writeHeader("Location", adminApi.getProfileUrl(authTokenData.hydraAccessToken));
|
||||
this.addCorsHeaders(res);
|
||||
// eslint-disable-next-line no-unsafe-finally
|
||||
return res.end();
|
||||
} catch (error) {
|
||||
return this.errorToResponse(error, res);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.errorToResponse(error, res);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ const SECRET_KEY = process.env.SECRET_KEY || "THECODINGMACHINE_SECRET_KEY";
|
||||
const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == "true" : false;
|
||||
const API_URL = process.env.API_URL || "";
|
||||
const ADMIN_API_URL = process.env.ADMIN_API_URL || "";
|
||||
const ADMIN_URL = process.env.ADMIN_URL || "";
|
||||
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || "myapitoken";
|
||||
const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80;
|
||||
const JITSI_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL;
|
||||
@ -19,6 +20,7 @@ export {
|
||||
SECRET_KEY,
|
||||
API_URL,
|
||||
ADMIN_API_URL,
|
||||
ADMIN_URL,
|
||||
ADMIN_API_TOKEN,
|
||||
ALLOW_ARTILLERY,
|
||||
CPU_OVERHEAT_THRESHOLD,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ADMIN_API_TOKEN, ADMIN_API_URL } from "../Enum/EnvironmentVariable";
|
||||
import { ADMIN_API_TOKEN, ADMIN_API_URL, ADMIN_URL } from "../Enum/EnvironmentVariable";
|
||||
import Axios from "axios";
|
||||
import { GameRoomPolicyTypes } from "_Model/PusherRoom";
|
||||
import { CharacterTexture } from "./AdminApi/CharacterTexture";
|
||||
@ -141,6 +141,15 @@ class AdminApi {
|
||||
return data.data;
|
||||
});
|
||||
}
|
||||
|
||||
/*TODO add constant to use profile companny*/
|
||||
getProfileUrl(accessToken: string): string {
|
||||
if (!ADMIN_URL) {
|
||||
throw new Error("No admin backoffice set!");
|
||||
}
|
||||
|
||||
return ADMIN_URL + `/profile?token=${accessToken}`;
|
||||
}
|
||||
}
|
||||
|
||||
export const adminApi = new AdminApi();
|
||||
|
@ -6,13 +6,13 @@ import { adminApi, AdminBannedData } from "../Services/AdminApi";
|
||||
|
||||
export interface AuthTokenData {
|
||||
identifier: string; //will be a email if logged in or an uuid if anonymous
|
||||
hydraAccessToken?: string;
|
||||
}
|
||||
export const tokenInvalidException = "tokenInvalid";
|
||||
|
||||
class JWTTokenManager {
|
||||
public createAuthToken(identifier: string) {
|
||||
//TODO fix me 200d when ory authentication will be available
|
||||
return Jwt.sign({ identifier }, SECRET_KEY, { expiresIn: "200d" });
|
||||
public createAuthToken(identifier: string, hydraAccessToken?: string) {
|
||||
return Jwt.sign({ identifier, hydraAccessToken }, SECRET_KEY, { expiresIn: "30d" });
|
||||
}
|
||||
|
||||
public verifyJWTToken(token: string, ignoreExpiration: boolean = false): AuthTokenData {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Issuer, Client } from "openid-client";
|
||||
import { Issuer, Client, IntrospectionResponse } from "openid-client";
|
||||
import { OPID_CLIENT_ID, OPID_CLIENT_SECRET, OPID_CLIENT_ISSUER, FRONT_URL } from "../Enum/EnvironmentVariable";
|
||||
|
||||
const opidRedirectUri = FRONT_URL + "/jwt";
|
||||
@ -31,12 +31,31 @@ class OpenIDClient {
|
||||
});
|
||||
}
|
||||
|
||||
public getUserInfo(code: string, nonce: string): Promise<{ email: string; sub: string }> {
|
||||
public getUserInfo(code: string, nonce: string): Promise<{ email: string; sub: string; access_token: string }> {
|
||||
return this.initClient().then((client) => {
|
||||
return client.callback(opidRedirectUri, { code }, { nonce }).then((tokenSet) => {
|
||||
return client.userinfo(tokenSet);
|
||||
return client.userinfo(tokenSet).then((res) => {
|
||||
return {
|
||||
...res,
|
||||
email: res.email as string,
|
||||
sub: res.sub,
|
||||
access_token: tokenSet.access_token as string,
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public logoutUser(token: string): Promise<void> {
|
||||
return this.initClient().then((client) => {
|
||||
return client.revoke(token);
|
||||
});
|
||||
}
|
||||
|
||||
public checkTokenAuth(token: string): Promise<IntrospectionResponse> {
|
||||
return this.initClient().then((client) => {
|
||||
return client.userinfo(token);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user