Improve profile menu to implement user identity connected URL (#2055)

#  Improve profile menu to implement user identity connected URL

- Create URL and get user identity
- Check if user is connected or show connexion button
- Improve profile to check before user data and don't display error webpage in menu
This commit is contained in:
grégoire parant 2022-04-14 10:17:59 +02:00 committed by GitHub
parent a3fd891530
commit a13c7e9e2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 116 additions and 18 deletions

View File

@ -9,7 +9,7 @@ class AnalyticsClient {
constructor() { constructor() {
if (POSTHOG_API_KEY && POSTHOG_URL) { if (POSTHOG_API_KEY && POSTHOG_URL) {
this.posthogPromise = import("posthog-js").then(({ default: posthog }) => { this.posthogPromise = import("posthog-js").then(({ default: posthog }) => {
posthog.init(POSTHOG_API_KEY, { api_host: POSTHOG_URL, disable_cookie: true }); posthog.init(POSTHOG_API_KEY, { api_host: POSTHOG_URL });
//the posthog toolbar need a reference in window to be able to work //the posthog toolbar need a reference in window to be able to work
window.posthog = posthog; window.posthog = posthog;
return posthog; return posthog;

View File

@ -1,7 +1,13 @@
<script lang="ts"> <script lang="ts">
import { gameManager } from "../../Phaser/Game/GameManager"; import { gameManager } from "../../Phaser/Game/GameManager";
import { SelectCompanionScene, SelectCompanionSceneName } from "../../Phaser/Login/SelectCompanionScene"; import { SelectCompanionScene, SelectCompanionSceneName } from "../../Phaser/Login/SelectCompanionScene";
import { menuIconVisiblilityStore, menuVisiblilityStore, userIsConnected } from "../../Stores/MenuStore"; import {
menuIconVisiblilityStore,
menuVisiblilityStore,
userIsConnected,
profileAvailable,
getProfileUrl,
} from "../../Stores/MenuStore";
import { selectCompanionSceneVisibleStore } from "../../Stores/SelectCompanionStore"; import { selectCompanionSceneVisibleStore } from "../../Stores/SelectCompanionStore";
import { LoginScene, LoginSceneName } from "../../Phaser/Login/LoginScene"; import { LoginScene, LoginSceneName } from "../../Phaser/Login/LoginScene";
import { loginSceneVisibleStore } from "../../Stores/LoginSceneStore"; import { loginSceneVisibleStore } from "../../Stores/LoginSceneStore";
@ -9,7 +15,6 @@
import { SelectCharacterScene, SelectCharacterSceneName } from "../../Phaser/Login/SelectCharacterScene"; import { SelectCharacterScene, SelectCharacterSceneName } from "../../Phaser/Login/SelectCharacterScene";
import { connectionManager } from "../../Connexion/ConnectionManager"; import { connectionManager } from "../../Connexion/ConnectionManager";
import { PROFILE_URL } from "../../Enum/EnvironmentVariable"; import { PROFILE_URL } from "../../Enum/EnvironmentVariable";
import { localUserStore } from "../../Connexion/LocalUserStore";
import { EnableCameraScene, EnableCameraSceneName } from "../../Phaser/Login/EnableCameraScene"; import { EnableCameraScene, EnableCameraSceneName } from "../../Phaser/Login/EnableCameraScene";
import { enableCameraSceneVisibilityStore } from "../../Stores/MediaStore"; import { enableCameraSceneVisibilityStore } from "../../Stores/MediaStore";
import btnProfileSubMenuCamera from "../images/btn-menu-profile-camera.svg"; import btnProfileSubMenuCamera from "../images/btn-menu-profile-camera.svg";
@ -47,10 +52,6 @@
return connectionManager.logout(); return connectionManager.logout();
} }
function getProfileUrl() {
return PROFILE_URL + `?token=${localUserStore.getAuthToken()}`;
}
function openEnableCameraScene() { function openEnableCameraScene() {
disableMenuStores(); disableMenuStores();
enableCameraSceneVisibilityStore.showEnableCameraScene(); enableCameraSceneVisibilityStore.showEnableCameraScene();
@ -81,7 +82,7 @@
</div> </div>
<div class="content"> <div class="content">
{#if $userIsConnected} {#if $userIsConnected && $profileAvailable}
<section> <section>
{#if PROFILE_URL != undefined} {#if PROFILE_URL != undefined}
<iframe title="profile" src={getProfileUrl()} /> <iframe title="profile" src={getProfileUrl()} />

View File

@ -290,12 +290,12 @@ class ConnectionManager {
); );
connection.onConnectError((error: object) => { connection.onConnectError((error: object) => {
console.log("An error occurred while connecting to socket server. Retrying"); console.log("onConnectError => An error occurred while connecting to socket server. Retrying");
reject(error); reject(error);
}); });
connection.connectionErrorStream.subscribe((event: CloseEvent) => { connection.connectionErrorStream.subscribe((event: CloseEvent) => {
console.log("An error occurred while connecting to socket server. Retrying"); console.log("connectionErrorStream => An error occurred while connecting to socket server. Retrying");
reject( reject(
new Error( new Error(
"An error occurred while connecting to socket server. Retrying. Code: " + "An error occurred while connecting to socket server. Retrying. Code: " +

View File

@ -23,6 +23,7 @@ export const DISPLAY_TERMS_OF_USE = getEnvConfig("DISPLAY_TERMS_OF_USE") == "tru
export const NODE_ENV = getEnvConfig("NODE_ENV") || "development"; export const NODE_ENV = getEnvConfig("NODE_ENV") || "development";
export const CONTACT_URL = getEnvConfig("CONTACT_URL") || undefined; export const CONTACT_URL = getEnvConfig("CONTACT_URL") || undefined;
export const PROFILE_URL = getEnvConfig("PROFILE_URL") || undefined; export const PROFILE_URL = getEnvConfig("PROFILE_URL") || undefined;
export const IDENTITY_URL = getEnvConfig("IDENTITY_URL") || undefined;
export const POSTHOG_API_KEY: string = (getEnvConfig("POSTHOG_API_KEY") as string) || ""; export const POSTHOG_API_KEY: string = (getEnvConfig("POSTHOG_API_KEY") as string) || "";
export const POSTHOG_URL = getEnvConfig("POSTHOG_URL") || undefined; export const POSTHOG_URL = getEnvConfig("POSTHOG_URL") || undefined;
export const DISABLE_ANONYMOUS: boolean = getEnvConfig("DISABLE_ANONYMOUS") === "true"; export const DISABLE_ANONYMOUS: boolean = getEnvConfig("DISABLE_ANONYMOUS") === "true";

View File

@ -1,17 +1,27 @@
import { get, writable } from "svelte/store"; import { get, writable } from "svelte/store";
import Timeout = NodeJS.Timeout; import Timeout = NodeJS.Timeout;
import { userIsAdminStore } from "./GameStore"; import { userIsAdminStore } from "./GameStore";
import { CONTACT_URL } from "../Enum/EnvironmentVariable"; import { CONTACT_URL, IDENTITY_URL, PROFILE_URL } from "../Enum/EnvironmentVariable";
import { analyticsClient } from "../Administration/AnalyticsClient"; import { analyticsClient } from "../Administration/AnalyticsClient";
import type { Translation } from "../i18n/i18n-types"; import type { Translation } from "../i18n/i18n-types";
import axios from "axios";
import { localUserStore } from "../Connexion/LocalUserStore";
export const menuIconVisiblilityStore = writable(false); export const menuIconVisiblilityStore = writable(false);
export const menuVisiblilityStore = writable(false); export const menuVisiblilityStore = writable(false);
menuVisiblilityStore.subscribe((value) => {
if (value) analyticsClient.openedMenu();
});
export const menuInputFocusStore = writable(false); export const menuInputFocusStore = writable(false);
export const userIsConnected = writable(false); export const userIsConnected = writable(false);
export const profileAvailable = writable(true);
menuVisiblilityStore.subscribe((value) => {
if (value) analyticsClient.openedMenu();
if (userIsConnected && value && IDENTITY_URL != null) {
axios.get(getMeUrl()).catch((err) => {
console.error("menuVisiblilityStore => err => ", err);
profileAvailable.set(false);
});
}
});
let warningContainerTimeout: Timeout | null = null; let warningContainerTimeout: Timeout | null = null;
function createWarningContainerStore() { function createWarningContainerStore() {
@ -173,3 +183,11 @@ export function handleMenuUnregisterEvent(menuName: string) {
subMenusStore.removeScriptingMenu(menuName); subMenusStore.removeScriptingMenu(menuName);
customMenuIframe.delete(menuName); customMenuIframe.delete(menuName);
} }
export function getProfileUrl() {
return PROFILE_URL + `?token=${localUserStore.getAuthToken()}`;
}
export function getMeUrl() {
return IDENTITY_URL + `?token=${localUserStore.getAuthToken()}`;
}

View File

@ -28,6 +28,7 @@ export default defineConfig({
"ADMIN_URL", "ADMIN_URL",
"CONTACT_URL", "CONTACT_URL",
"PROFILE_URL", "PROFILE_URL",
"IDENTITY_URL",
"ICON_URL", "ICON_URL",
"DEBUG_MODE", "DEBUG_MODE",
"STUN_SERVER", "STUN_SERVER",

View File

@ -1,6 +1,6 @@
import { v4 } from "uuid"; import { v4 } from "uuid";
import { BaseHttpController } from "./BaseHttpController"; import { BaseHttpController } from "./BaseHttpController";
import { adminApi } from "../Services/AdminApi"; import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager"; import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
import { parse } from "query-string"; import { parse } from "query-string";
import { openIDClient } from "../Services/OpenIDClient"; import { openIDClient } from "../Services/OpenIDClient";
@ -19,6 +19,7 @@ export class AuthenticateController extends BaseHttpController {
this.register(); this.register();
this.anonymLogin(); this.anonymLogin();
this.profileCallback(); this.profileCallback();
this.me();
} }
openIDLogin() { openIDLogin() {
@ -180,7 +181,7 @@ export class AuthenticateController extends BaseHttpController {
if (!code && !nonce) { if (!code && !nonce) {
return res.json({ ...resUserData, authToken: token }); return res.json({ ...resUserData, authToken: token });
} }
console.error("Token cannot to be check on OpenId provider"); console.error("Token cannot be checked on OpenId provider");
res.status(500); res.status(500);
res.send("User cannot to be connected on openid provider"); res.send("User cannot to be connected on openid provider");
return; return;
@ -255,7 +256,7 @@ export class AuthenticateController extends BaseHttpController {
try { try {
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false); const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
if (authTokenData.accessToken == undefined) { if (authTokenData.accessToken == undefined) {
throw Error("Token cannot to be logout on Hydra"); throw Error("Token cannot be logout on Hydra");
} }
await openIDClient.logoutUser(authTokenData.accessToken); await openIDClient.logoutUser(authTokenData.accessToken);
} catch (error) { } catch (error) {
@ -412,7 +413,7 @@ export class AuthenticateController extends BaseHttpController {
try { try {
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false); const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
if (authTokenData.accessToken == undefined) { if (authTokenData.accessToken == undefined) {
throw Error("Token cannot to be check on Hydra"); throw Error("Token cannot be checked on OpenID connect provider");
} }
await openIDClient.checkTokenAuth(authTokenData.accessToken); await openIDClient.checkTokenAuth(authTokenData.accessToken);
@ -432,4 +433,80 @@ export class AuthenticateController extends BaseHttpController {
} }
}); });
} }
/**
* @openapi
* /me:
* get:
* description: ???
* parameters:
* - name: "token"
* in: "query"
* description: "A JWT authentication token ???"
* required: true
* type: "string"
* responses:
* 200:
* description: Data of user connected
*/
me() {
// @ts-ignore
this.app.get("/me", async (req, res): void => {
const { token } = parse(req.path_query);
try {
//verify connected by token
if (token != undefined) {
try {
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
if (authTokenData.accessToken == undefined) {
throw Error("Token cannot to be checked on Hydra");
}
const me = await openIDClient.checkTokenAuth(authTokenData.accessToken);
//get login profile
res.status(200);
res.json({ ...me });
return;
} catch (error) {
this.castErrorToResponse(error, res);
return;
}
}
} catch (error) {
console.error("me => ERROR", error);
this.castErrorToResponse(error, res);
return;
}
});
}
/**
*
* @param email
* @param playUri
* @param IPAddress
* @return FetchMemberDataByUuidResponse|object
* @private
*/
private async getUserByUserIdentifier(
email: string,
playUri: string,
IPAddress: string
): Promise<FetchMemberDataByUuidResponse | object> {
let data: FetchMemberDataByUuidResponse = {
email: email,
userUuid: email,
tags: [],
messages: [],
visitCardUrl: null,
textures: [],
userRoomToken: undefined,
};
try {
data = await adminApi.fetchMemberDataByUuid(email, playUri, IPAddress, []);
} catch (err) {
console.error("openIDCallback => fetchMemberDataByUuid", err);
}
return data;
}
} }