diff --git a/front/dist/resources/html/gameMenu.html b/front/dist/resources/html/gameMenu.html new file mode 100644 index 00000000..e69de29b diff --git a/front/src/Components/Menu/ProfileSubMenu.svelte b/front/src/Components/Menu/ProfileSubMenu.svelte index 2a2b7af0..39214b4f 100644 --- a/front/src/Components/Menu/ProfileSubMenu.svelte +++ b/front/src/Components/Menu/ProfileSubMenu.svelte @@ -1,15 +1,17 @@
+ {#if $userIsConnected} +
+ {#if PROFILE_URL != undefined} + + {/if} +
+
+ +
+ {:else} +
+ Sing in +
+ {/if}
-
-
-
-
@@ -74,6 +95,12 @@ align-items: center; margin-bottom: 20px; + iframe{ + width: 100%; + height: 50vh; + border: none; + } + button { height: 50px; width: 250px; diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index 9e245d3a..2010689c 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -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 + * TODO fix me to be move in game manager */ - public loadOpenIDScreen(): Promise { + 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 { 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 { @@ -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(); diff --git a/front/src/Connexion/LocalUserStore.ts b/front/src/Connexion/LocalUserStore.ts index 05c9613d..6c20aadb 100644 --- a/front/src/Connexion/LocalUserStore.ts +++ b/front/src/Connexion/LocalUserStore.ts @@ -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 { 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) { diff --git a/front/src/Connexion/Room.ts b/front/src/Connexion/Room.ts index be9b5294..0e624d78 100644 --- a/front/src/Connexion/Room.ts +++ b/front/src/Connexion/Room.ts @@ -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; + } } diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 248253a5..08672be1 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -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()); diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index 907162d4..60678822 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -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; diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index 068e1734..1bf18d8d 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -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(); diff --git a/front/src/Phaser/Login/LoginScene.ts b/front/src/Phaser/Login/LoginScene.ts index 0cca9ccd..7421934d 100644 --- a/front/src/Phaser/Login/LoginScene.ts +++ b/front/src/Phaser/Login/LoginScene.ts @@ -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); } diff --git a/front/src/Stores/LoginSceneStore.ts b/front/src/Stores/LoginSceneStore.ts index 6e2ea18b..4bd5b10a 100644 --- a/front/src/Stores/LoginSceneStore.ts +++ b/front/src/Stores/LoginSceneStore.ts @@ -1,3 +1,4 @@ import { writable } from "svelte/store"; export const loginSceneVisibleStore = writable(false); +export const loginSceneVisibleIframeStore = writable(false); diff --git a/front/src/Stores/MenuStore.ts b/front/src/Stores/MenuStore.ts index 024c742d..035ba174 100644 --- a/front/src/Stores/MenuStore.ts +++ b/front/src/Stores/MenuStore.ts @@ -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() { diff --git a/front/src/Stores/PlayersStore.ts b/front/src/Stores/PlayersStore.ts index 86ab136f..e6f5b1af 100644 --- a/front/src/Stores/PlayersStore.ts +++ b/front/src/Stores/PlayersStore.ts @@ -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; diff --git a/front/src/Url/UrlManager.ts b/front/src/Url/UrlManager.ts index 6d4949ab..f1e15db1 100644 --- a/front/src/Url/UrlManager.ts +++ b/front/src/Url/UrlManager.ts @@ -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); diff --git a/front/webpack.config.ts b/front/webpack.config.ts index 9d9a4a7b..48db1afa 100644 --- a/front/webpack.config.ts +++ b/front/webpack.config.ts @@ -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, diff --git a/pusher/src/Controller/AuthenticateController.ts b/pusher/src/Controller/AuthenticateController.ts index ffb6c048..000ac0ca 100644 --- a/pusher/src/Controller/AuthenticateController.ts +++ b/pusher/src/Controller/AuthenticateController.ts @@ -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); + } + }); + } } diff --git a/pusher/src/Enum/EnvironmentVariable.ts b/pusher/src/Enum/EnvironmentVariable.ts index ea8f22d8..4a078d90 100644 --- a/pusher/src/Enum/EnvironmentVariable.ts +++ b/pusher/src/Enum/EnvironmentVariable.ts @@ -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, diff --git a/pusher/src/Services/AdminApi.ts b/pusher/src/Services/AdminApi.ts index f33480ca..e53d00ae 100644 --- a/pusher/src/Services/AdminApi.ts +++ b/pusher/src/Services/AdminApi.ts @@ -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(); diff --git a/pusher/src/Services/JWTTokenManager.ts b/pusher/src/Services/JWTTokenManager.ts index 4711ccfd..24393084 100644 --- a/pusher/src/Services/JWTTokenManager.ts +++ b/pusher/src/Services/JWTTokenManager.ts @@ -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 { diff --git a/pusher/src/Services/OpenIDClient.ts b/pusher/src/Services/OpenIDClient.ts index 4fda8b5e..b6506a5e 100644 --- a/pusher/src/Services/OpenIDClient.ts +++ b/pusher/src/Services/OpenIDClient.ts @@ -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,13 +31,32 @@ 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 { + return this.initClient().then((client) => { + return client.revoke(token); + }); + } + + public checkTokenAuth(token: string): Promise { + return this.initClient().then((client) => { + return client.userinfo(token); + }); + } } export const openIDClient = new OpenIDClient();