From 6832fe49901ce28b3413000337a142cc9dae445c Mon Sep 17 00:00:00 2001 From: Lurkars Date: Thu, 21 Oct 2021 16:28:57 +0200 Subject: [PATCH 01/11] use OIDC without admin api, option to disable anonymous login --- .env.template | 1 + docker-compose.single-domain.yaml | 1 + docker-compose.yaml | 1 + front/src/Connexion/ConnectionManager.ts | 34 ++++++++++++++----- .../src/Controller/AuthenticateController.ts | 26 ++++++++------ pusher/src/Controller/IoSocketController.ts | 7 +++- pusher/src/Controller/MapController.ts | 9 +++-- pusher/src/Enum/EnvironmentVariable.ts | 1 + .../src/Services/AdminApi/MapDetailsData.ts | 1 + 9 files changed, 59 insertions(+), 22 deletions(-) diff --git a/.env.template b/.env.template index 715ebeec..f511b398 100644 --- a/.env.template +++ b/.env.template @@ -22,6 +22,7 @@ MAX_USERNAME_LENGTH=8 OPID_CLIENT_ID= OPID_CLIENT_SECRET= OPID_CLIENT_ISSUER= +DISABLE_ANONYMOUS= # If you want to have a contact page in your menu, you MUST set CONTACT_URL to the URL of the page that you want CONTACT_URL= \ No newline at end of file diff --git a/docker-compose.single-domain.yaml b/docker-compose.single-domain.yaml index 4e85d702..2e2cab7d 100644 --- a/docker-compose.single-domain.yaml +++ b/docker-compose.single-domain.yaml @@ -70,6 +70,7 @@ services: OPID_CLIENT_ID: $OPID_CLIENT_ID OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER + DISABLE_ANONYMOUS: $DISABLE_ANONYMOUS volumes: - ./pusher:/usr/src/app labels: diff --git a/docker-compose.yaml b/docker-compose.yaml index 4b3904dd..65dcc61f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -70,6 +70,7 @@ services: OPID_CLIENT_ID: $OPID_CLIENT_ID OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER + DISABLE_ANONYMOUS: $DISABLE_ANONYMOUS volumes: - ./pusher:/usr/src/app labels: diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index b346f450..c91e4590 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -42,11 +42,22 @@ class ConnectionManager { localUserStore.setAuthToken(null); //TODO fix me to redirect this URL by pusher - if (!this._currentRoom || !this._currentRoom.iframeAuthentication) { + if (!this._currentRoom) { loginSceneVisibleIframeStore.set(false); return null; } - const redirectUrl = new URL(`${this._currentRoom.iframeAuthentication}`); + + // also allow OIDC login without admin API by using pusher + let redirectUrl: URL; + if (this._currentRoom.iframeAuthentication) { + redirectUrl = new URL(`${this._currentRoom.iframeAuthentication}`); + } else { + // need origin if PUSHER_URL is relative (in Single-Domain-Deployment) + redirectUrl = new URL( + `${PUSHER_URL}/login-screen`, + !PUSHER_URL.startsWith("http:") || !PUSHER_URL.startsWith("https:") ? window.location.origin : undefined + ); + } redirectUrl.searchParams.append("state", state); redirectUrl.searchParams.append("nonce", nonce); redirectUrl.searchParams.append("playUri", this._currentRoom.key); @@ -204,13 +215,18 @@ class ConnectionManager { } public async anonymousLogin(isBenchmark: boolean = false): Promise { - const data = await Axios.post(`${PUSHER_URL}/anonymLogin`).then((res) => res.data); - this.localUser = new LocalUser(data.userUuid, []); - this.authToken = data.authToken; - if (!isBenchmark) { - // In benchmark, we don't have a local storage. - localUserStore.saveUser(this.localUser); - localUserStore.setAuthToken(this.authToken); + try { + const data = await Axios.post(`${PUSHER_URL}/anonymLogin`).then((res) => res.data); + this.localUser = new LocalUser(data.userUuid, []); + this.authToken = data.authToken; + if (!isBenchmark) { + // In benchmark, we don't have a local storage. + localUserStore.saveUser(this.localUser); + localUserStore.setAuthToken(this.authToken); + } + } catch (error) { + // anonymous login failed (through 403 DISABLE_ANONYMOUS) + this.loadOpenIDScreen(); } } diff --git a/pusher/src/Controller/AuthenticateController.ts b/pusher/src/Controller/AuthenticateController.ts index 972cc102..2dafe065 100644 --- a/pusher/src/Controller/AuthenticateController.ts +++ b/pusher/src/Controller/AuthenticateController.ts @@ -5,6 +5,7 @@ import { adminApi } from "../Services/AdminApi"; import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager"; import { parse } from "query-string"; import { openIDClient } from "../Services/OpenIDClient"; +import { DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable"; export interface TokenInterface { userUuid: string; @@ -175,16 +176,21 @@ export class AuthenticateController extends BaseController { console.warn("Login request was aborted"); }); - const userUuid = v4(); - const authToken = jwtTokenManager.createAuthToken(userUuid); - res.writeStatus("200 OK"); - this.addCorsHeaders(res); - res.end( - JSON.stringify({ - authToken, - userUuid, - }) - ); + if (DISABLE_ANONYMOUS) { + res.writeStatus("403 FORBIDDEN"); + res.end(); + } else { + const userUuid = v4(); + const authToken = jwtTokenManager.createAuthToken(userUuid); + res.writeStatus("200 OK"); + this.addCorsHeaders(res); + res.end( + JSON.stringify({ + authToken, + userUuid, + }) + ); + } }); } diff --git a/pusher/src/Controller/IoSocketController.ts b/pusher/src/Controller/IoSocketController.ts index 0466100c..500f4142 100644 --- a/pusher/src/Controller/IoSocketController.ts +++ b/pusher/src/Controller/IoSocketController.ts @@ -26,7 +26,7 @@ import { jwtTokenManager, tokenInvalidException } from "../Services/JWTTokenMana import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi"; import { SocketManager, socketManager } from "../Services/SocketManager"; import { emitInBatch } from "../Services/IoSocketHelpers"; -import { ADMIN_API_TOKEN, ADMIN_API_URL, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable"; +import { ADMIN_API_TOKEN, ADMIN_API_URL, SOCKET_IDLE_TIMER, DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable"; import { Zone } from "_Model/Zone"; import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface"; import { v4 } from "uuid"; @@ -175,6 +175,11 @@ export class IoSocketController { const tokenData = token && typeof token === "string" ? jwtTokenManager.verifyJWTToken(token) : null; + + if (DISABLE_ANONYMOUS && !tokenData) { + throw new Error("Expecting token"); + } + const userIdentifier = tokenData ? tokenData.identifier : ""; let memberTags: string[] = []; diff --git a/pusher/src/Controller/MapController.ts b/pusher/src/Controller/MapController.ts index f775b50c..437bac6a 100644 --- a/pusher/src/Controller/MapController.ts +++ b/pusher/src/Controller/MapController.ts @@ -2,9 +2,9 @@ import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js"; import { BaseController } from "./BaseController"; import { parse } from "query-string"; import { adminApi } from "../Services/AdminApi"; -import { ADMIN_API_URL } from "../Enum/EnvironmentVariable"; +import { ADMIN_API_URL, DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable"; import { GameRoomPolicyTypes } from "../Model/PusherRoom"; -import { MapDetailsData } from "../Services/AdminApi/MapDetailsData"; +import { isMapDetailsData, MapDetailsData } from "../Services/AdminApi/MapDetailsData"; import { socketManager } from "../Services/SocketManager"; import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager"; import { v4 } from "uuid"; @@ -64,6 +64,7 @@ export class MapController extends BaseController { tags: [], textures: [], contactPage: undefined, + authenticationMandatory: DISABLE_ANONYMOUS, } as MapDetailsData) ); @@ -87,6 +88,10 @@ export class MapController extends BaseController { } const mapDetails = await adminApi.fetchMapDetails(query.playUri as string, userId); + if (isMapDetailsData(mapDetails) && DISABLE_ANONYMOUS) { + (mapDetails as MapDetailsData).authenticationMandatory = true; + } + res.writeStatus("200 OK"); this.addCorsHeaders(res); res.end(JSON.stringify(mapDetails)); diff --git a/pusher/src/Enum/EnvironmentVariable.ts b/pusher/src/Enum/EnvironmentVariable.ts index ab1ce110..be81871d 100644 --- a/pusher/src/Enum/EnvironmentVariable.ts +++ b/pusher/src/Enum/EnvironmentVariable.ts @@ -15,6 +15,7 @@ export const FRONT_URL = process.env.FRONT_URL || "http://localhost"; export const OPID_CLIENT_ID = process.env.OPID_CLIENT_ID || ""; export const OPID_CLIENT_SECRET = process.env.OPID_CLIENT_SECRET || ""; export const OPID_CLIENT_ISSUER = process.env.OPID_CLIENT_ISSUER || ""; +export const DISABLE_ANONYMOUS = process.env.DISABLE_ANONYMOUS ? process.env.DISABLE_ANONYMOUS == "true" : false; export { SECRET_KEY, diff --git a/pusher/src/Services/AdminApi/MapDetailsData.ts b/pusher/src/Services/AdminApi/MapDetailsData.ts index 278b81bb..7a1f57ff 100644 --- a/pusher/src/Services/AdminApi/MapDetailsData.ts +++ b/pusher/src/Services/AdminApi/MapDetailsData.ts @@ -16,6 +16,7 @@ export const isMapDetailsData = new tg.IsInterface() tags: tg.isArray(tg.isString), textures: tg.isArray(isCharacterTexture), contactPage: tg.isUnion(tg.isString, tg.isUndefined), + authenticationMandatory: tg.isUnion(tg.isBoolean, tg.isUndefined), }) .get(); From 78e0d1bead402348f36a3341ceea68b3f8e462e3 Mon Sep 17 00:00:00 2001 From: Lurkars Date: Thu, 21 Oct 2021 16:53:24 +0200 Subject: [PATCH 02/11] fix linter, cleanup --- front/src/Connexion/ConnectionManager.ts | 19 +++++++------------ pusher/src/Controller/MapController.ts | 2 +- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index c91e4590..aae8440a 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -215,18 +215,13 @@ class ConnectionManager { } public async anonymousLogin(isBenchmark: boolean = false): Promise { - try { - const data = await Axios.post(`${PUSHER_URL}/anonymLogin`).then((res) => res.data); - this.localUser = new LocalUser(data.userUuid, []); - this.authToken = data.authToken; - if (!isBenchmark) { - // In benchmark, we don't have a local storage. - localUserStore.saveUser(this.localUser); - localUserStore.setAuthToken(this.authToken); - } - } catch (error) { - // anonymous login failed (through 403 DISABLE_ANONYMOUS) - this.loadOpenIDScreen(); + const data = await Axios.post(`${PUSHER_URL}/anonymLogin`).then((res) => res.data); + this.localUser = new LocalUser(data.userUuid, []); + this.authToken = data.authToken; + if (!isBenchmark) { + // In benchmark, we don't have a local storage. + localUserStore.saveUser(this.localUser); + localUserStore.setAuthToken(this.authToken); } } diff --git a/pusher/src/Controller/MapController.ts b/pusher/src/Controller/MapController.ts index 437bac6a..18748d9e 100644 --- a/pusher/src/Controller/MapController.ts +++ b/pusher/src/Controller/MapController.ts @@ -89,7 +89,7 @@ export class MapController extends BaseController { const mapDetails = await adminApi.fetchMapDetails(query.playUri as string, userId); if (isMapDetailsData(mapDetails) && DISABLE_ANONYMOUS) { - (mapDetails as MapDetailsData).authenticationMandatory = true; + mapDetails.authenticationMandatory = true; } res.writeStatus("200 OK"); From fa233e13c6fa561964578a68198e4500f125725a Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Fri, 12 Nov 2021 16:43:37 +0100 Subject: [PATCH 03/11] Changes ANONYMOUS variable This variable will be use to mandatory login user in self hosted --- front/src/Connexion/Room.ts | 6 ++--- front/src/Enum/EnvironmentVariable.ts | 1 + .../src/Controller/AuthenticateController.ts | 26 ++++++++++++------- pusher/src/Controller/IoSocketController.ts | 6 ++++- pusher/src/Controller/MapController.ts | 9 +++++-- pusher/src/Enum/EnvironmentVariable.ts | 2 ++ .../src/Services/AdminApi/MapDetailsData.ts | 1 + 7 files changed, 35 insertions(+), 16 deletions(-) diff --git a/front/src/Connexion/Room.ts b/front/src/Connexion/Room.ts index 860223c6..0737de82 100644 --- a/front/src/Connexion/Room.ts +++ b/front/src/Connexion/Room.ts @@ -1,5 +1,5 @@ import Axios from "axios"; -import { CONTACT_URL, PUSHER_URL } from "../Enum/EnvironmentVariable"; +import { CONTACT_URL, PUSHER_URL, DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable"; import type { CharacterTexture } from "./LocalUser"; import { localUserStore } from "./LocalUserStore"; @@ -14,7 +14,7 @@ export interface RoomRedirect { export class Room { public readonly id: string; public readonly isPublic: boolean; - private _authenticationMandatory: boolean = false; + private _authenticationMandatory: boolean = DISABLE_ANONYMOUS as boolean; private _iframeAuthentication?: string; private _mapUrl: string | undefined; private _textures: CharacterTexture[] | undefined; @@ -106,7 +106,7 @@ export class Room { this._mapUrl = data.mapUrl; this._textures = data.textures; this._group = data.group; - this._authenticationMandatory = data.authenticationMandatory || false; + this._authenticationMandatory = data.authenticationMandatory || (DISABLE_ANONYMOUS as boolean); this._iframeAuthentication = data.iframeAuthentication; this._contactPage = data.contactPage || CONTACT_URL; return new MapDetail(data.mapUrl, data.textures); diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index cf76a87d..cab31370 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -23,6 +23,7 @@ export const CONTACT_URL = process.env.CONTACT_URL || undefined; export const PROFILE_URL = process.env.PROFILE_URL || undefined; export const POSTHOG_API_KEY: string = (process.env.POSTHOG_API_KEY as string) || ""; export const POSTHOG_URL = process.env.POSTHOG_URL || undefined; +export const DISABLE_ANONYMOUS = process.env.DISABLE_ANONYMOUS || false; export const isMobile = (): boolean => window.innerWidth <= 800 || window.innerHeight <= 600; diff --git a/pusher/src/Controller/AuthenticateController.ts b/pusher/src/Controller/AuthenticateController.ts index 972cc102..1e91667f 100644 --- a/pusher/src/Controller/AuthenticateController.ts +++ b/pusher/src/Controller/AuthenticateController.ts @@ -5,6 +5,7 @@ import { adminApi } from "../Services/AdminApi"; import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager"; import { parse } from "query-string"; import { openIDClient } from "../Services/OpenIDClient"; +import { DISABLE_ANONYMOUS } from "_Enum/EnvironmentVariable"; export interface TokenInterface { userUuid: string; @@ -175,16 +176,21 @@ export class AuthenticateController extends BaseController { console.warn("Login request was aborted"); }); - const userUuid = v4(); - const authToken = jwtTokenManager.createAuthToken(userUuid); - res.writeStatus("200 OK"); - this.addCorsHeaders(res); - res.end( - JSON.stringify({ - authToken, - userUuid, - }) - ); + if (DISABLE_ANONYMOUS) { + res.writeStatus("403 FORBIDDEN"); + res.end(); + } else { + const userUuid = v4(); + const authToken = jwtTokenManager.createAuthToken(userUuid); + res.writeStatus("200 OK"); + this.addCorsHeaders(res); + res.end( + JSON.stringify({ + authToken, + userUuid, + }) + ); + } }); } diff --git a/pusher/src/Controller/IoSocketController.ts b/pusher/src/Controller/IoSocketController.ts index 0466100c..554b77d8 100644 --- a/pusher/src/Controller/IoSocketController.ts +++ b/pusher/src/Controller/IoSocketController.ts @@ -26,7 +26,7 @@ import { jwtTokenManager, tokenInvalidException } from "../Services/JWTTokenMana import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi"; import { SocketManager, socketManager } from "../Services/SocketManager"; import { emitInBatch } from "../Services/IoSocketHelpers"; -import { ADMIN_API_TOKEN, ADMIN_API_URL, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable"; +import { ADMIN_API_TOKEN, ADMIN_API_URL, DISABLE_ANONYMOUS, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable"; import { Zone } from "_Model/Zone"; import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface"; import { v4 } from "uuid"; @@ -177,6 +177,10 @@ export class IoSocketController { token && typeof token === "string" ? jwtTokenManager.verifyJWTToken(token) : null; const userIdentifier = tokenData ? tokenData.identifier : ""; + if (DISABLE_ANONYMOUS && !tokenData) { + throw new Error("Expecting token"); + } + let memberTags: string[] = []; let memberVisitCardUrl: string | null = null; let memberMessages: unknown; diff --git a/pusher/src/Controller/MapController.ts b/pusher/src/Controller/MapController.ts index f775b50c..18748d9e 100644 --- a/pusher/src/Controller/MapController.ts +++ b/pusher/src/Controller/MapController.ts @@ -2,9 +2,9 @@ import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js"; import { BaseController } from "./BaseController"; import { parse } from "query-string"; import { adminApi } from "../Services/AdminApi"; -import { ADMIN_API_URL } from "../Enum/EnvironmentVariable"; +import { ADMIN_API_URL, DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable"; import { GameRoomPolicyTypes } from "../Model/PusherRoom"; -import { MapDetailsData } from "../Services/AdminApi/MapDetailsData"; +import { isMapDetailsData, MapDetailsData } from "../Services/AdminApi/MapDetailsData"; import { socketManager } from "../Services/SocketManager"; import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager"; import { v4 } from "uuid"; @@ -64,6 +64,7 @@ export class MapController extends BaseController { tags: [], textures: [], contactPage: undefined, + authenticationMandatory: DISABLE_ANONYMOUS, } as MapDetailsData) ); @@ -87,6 +88,10 @@ export class MapController extends BaseController { } const mapDetails = await adminApi.fetchMapDetails(query.playUri as string, userId); + if (isMapDetailsData(mapDetails) && DISABLE_ANONYMOUS) { + mapDetails.authenticationMandatory = true; + } + res.writeStatus("200 OK"); this.addCorsHeaders(res); res.end(JSON.stringify(mapDetails)); diff --git a/pusher/src/Enum/EnvironmentVariable.ts b/pusher/src/Enum/EnvironmentVariable.ts index ab1ce110..43bfc7bf 100644 --- a/pusher/src/Enum/EnvironmentVariable.ts +++ b/pusher/src/Enum/EnvironmentVariable.ts @@ -15,6 +15,8 @@ export const FRONT_URL = process.env.FRONT_URL || "http://localhost"; export const OPID_CLIENT_ID = process.env.OPID_CLIENT_ID || ""; export const OPID_CLIENT_SECRET = process.env.OPID_CLIENT_SECRET || ""; export const OPID_CLIENT_ISSUER = process.env.OPID_CLIENT_ISSUER || ""; +export const OPID_CLIENT_REDIREC_URL = process.env.OPID_CLIENT_REDIREC_URL || FRONT_URL + "/jwt"; +export const DISABLE_ANONYMOUS = process.env.DISABLE_ANONYMOUS || false; export { SECRET_KEY, diff --git a/pusher/src/Services/AdminApi/MapDetailsData.ts b/pusher/src/Services/AdminApi/MapDetailsData.ts index 278b81bb..7a1f57ff 100644 --- a/pusher/src/Services/AdminApi/MapDetailsData.ts +++ b/pusher/src/Services/AdminApi/MapDetailsData.ts @@ -16,6 +16,7 @@ export const isMapDetailsData = new tg.IsInterface() tags: tg.isArray(tg.isString), textures: tg.isArray(isCharacterTexture), contactPage: tg.isUnion(tg.isString, tg.isUndefined), + authenticationMandatory: tg.isUnion(tg.isBoolean, tg.isUndefined), }) .get(); From c21ea8803a717da65b9696acee7d0b07aa834ded Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Fri, 12 Nov 2021 16:52:44 +0100 Subject: [PATCH 04/11] Add OPID redirect url Is important to defined redirect url to be connected with openid provider --- .env.template | 1 + docker-compose.single-domain.yaml | 2 ++ docker-compose.yaml | 2 ++ 3 files changed, 5 insertions(+) diff --git a/.env.template b/.env.template index f511b398..2e9900a7 100644 --- a/.env.template +++ b/.env.template @@ -22,6 +22,7 @@ MAX_USERNAME_LENGTH=8 OPID_CLIENT_ID= OPID_CLIENT_SECRET= OPID_CLIENT_ISSUER= +OPID_CLIENT_REDIREC_URL= DISABLE_ANONYMOUS= # If you want to have a contact page in your menu, you MUST set CONTACT_URL to the URL of the page that you want diff --git a/docker-compose.single-domain.yaml b/docker-compose.single-domain.yaml index 2e2cab7d..4522cbe9 100644 --- a/docker-compose.single-domain.yaml +++ b/docker-compose.single-domain.yaml @@ -40,6 +40,7 @@ services: TURN_USER: "" TURN_PASSWORD: "" START_ROOM_URL: "$START_ROOM_URL" + DISABLE_ANONYMOUS: "$DISABLE_ANONYMOUS" command: yarn run start volumes: - ./front:/usr/src/app @@ -70,6 +71,7 @@ services: OPID_CLIENT_ID: $OPID_CLIENT_ID OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER + OPID_CLIENT_REDIREC_URL: $OPID_CLIENT_REDIREC_URL DISABLE_ANONYMOUS: $DISABLE_ANONYMOUS volumes: - ./pusher:/usr/src/app diff --git a/docker-compose.yaml b/docker-compose.yaml index f0bcc841..9de0afff 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -43,6 +43,7 @@ services: START_ROOM_URL: "$START_ROOM_URL" MAX_PER_GROUP: "$MAX_PER_GROUP" MAX_USERNAME_LENGTH: "$MAX_USERNAME_LENGTH" + DISABLE_ANONYMOUS: "$DISABLE_ANONYMOUS" command: yarn run start volumes: - ./front:/usr/src/app @@ -71,6 +72,7 @@ services: OPID_CLIENT_ID: $OPID_CLIENT_ID OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER + OPID_CLIENT_REDIREC_URL: $OPID_CLIENT_REDIREC_URL DISABLE_ANONYMOUS: $DISABLE_ANONYMOUS volumes: - ./pusher:/usr/src/app From 24a1f324c700116e49978365c6c69a09d69c9504 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Fri, 12 Nov 2021 19:35:34 +0100 Subject: [PATCH 05/11] Add OpenId login url and provider to login user --- .env.template | 1 + docker-compose.yaml | 1 + front/webpack.config.ts | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.env.template b/.env.template index 2e9900a7..e7cdffbc 100644 --- a/.env.template +++ b/.env.template @@ -23,6 +23,7 @@ OPID_CLIENT_ID= OPID_CLIENT_SECRET= OPID_CLIENT_ISSUER= OPID_CLIENT_REDIREC_URL= +OPID_LOGIN_SCREEN_PROVIDER= DISABLE_ANONYMOUS= # If you want to have a contact page in your menu, you MUST set CONTACT_URL to the URL of the page that you want diff --git a/docker-compose.yaml b/docker-compose.yaml index 9de0afff..cc27a78a 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -44,6 +44,7 @@ services: MAX_PER_GROUP: "$MAX_PER_GROUP" MAX_USERNAME_LENGTH: "$MAX_USERNAME_LENGTH" DISABLE_ANONYMOUS: "$DISABLE_ANONYMOUS" + OPID_LOGIN_SCREEN_PROVIDER: "$OPID_LOGIN_SCREEN_PROVIDER" command: yarn run start volumes: - ./front:/usr/src/app diff --git a/front/webpack.config.ts b/front/webpack.config.ts index 003db1fc..9d18d51f 100644 --- a/front/webpack.config.ts +++ b/front/webpack.config.ts @@ -7,7 +7,6 @@ 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 { POSTHOG_API_KEY, PROFILE_URL } from "./src/Enum/EnvironmentVariable"; const mode = process.env.NODE_ENV ?? "development"; const buildNpmTypingsForApi = !!process.env.BUILD_TYPINGS; @@ -208,6 +207,8 @@ module.exports = { POSTHOG_API_KEY: null, POSTHOG_URL: null, NODE_ENV: mode, + DISABLE_ANONYMOUS: false, + OPID_LOGIN_SCREEN_PROVIDER: null, }), ], } as Configuration & WebpackDevServer.Configuration; From f905426ebda77b0be624dd9476d5ebaefe281d53 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Fri, 12 Nov 2021 19:36:13 +0100 Subject: [PATCH 06/11] Add new variable login provider to connect user --- front/src/Connexion/Room.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/front/src/Connexion/Room.ts b/front/src/Connexion/Room.ts index 0737de82..535d2f8d 100644 --- a/front/src/Connexion/Room.ts +++ b/front/src/Connexion/Room.ts @@ -1,5 +1,5 @@ import Axios from "axios"; -import { CONTACT_URL, PUSHER_URL, DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable"; +import { CONTACT_URL, PUSHER_URL, DISABLE_ANONYMOUS, OPID_LOGIN_SCREEN_PROVIDER } from "../Enum/EnvironmentVariable"; import type { CharacterTexture } from "./LocalUser"; import { localUserStore } from "./LocalUserStore"; @@ -15,7 +15,7 @@ export class Room { public readonly id: string; public readonly isPublic: boolean; private _authenticationMandatory: boolean = DISABLE_ANONYMOUS as boolean; - private _iframeAuthentication?: string; + private _iframeAuthentication?: string = OPID_LOGIN_SCREEN_PROVIDER; private _mapUrl: string | undefined; private _textures: CharacterTexture[] | undefined; private instance: string | undefined; @@ -107,7 +107,7 @@ export class Room { this._textures = data.textures; this._group = data.group; this._authenticationMandatory = data.authenticationMandatory || (DISABLE_ANONYMOUS as boolean); - this._iframeAuthentication = data.iframeAuthentication; + this._iframeAuthentication = data.iframeAuthentication || OPID_LOGIN_SCREEN_PROVIDER; this._contactPage = data.contactPage || CONTACT_URL; return new MapDetail(data.mapUrl, data.textures); } From 15cdc54ec35534457d2e6482fab9c6a9d44e554a Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Fri, 12 Nov 2021 19:36:37 +0100 Subject: [PATCH 07/11] Add evnvironment Enum for OPID_LOGIN_SCREEN_PROVIDER variable --- front/src/Enum/EnvironmentVariable.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index cab31370..644b7a77 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -24,6 +24,7 @@ export const PROFILE_URL = process.env.PROFILE_URL || undefined; export const POSTHOG_API_KEY: string = (process.env.POSTHOG_API_KEY as string) || ""; export const POSTHOG_URL = process.env.POSTHOG_URL || undefined; export const DISABLE_ANONYMOUS = process.env.DISABLE_ANONYMOUS || false; +export const OPID_LOGIN_SCREEN_PROVIDER = process.env.OPID_LOGIN_SCREEN_PROVIDER; export const isMobile = (): boolean => window.innerWidth <= 800 || window.innerHeight <= 600; From 7d0b573d37fff03531e4771f47f4e221f62688ae Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Fri, 12 Nov 2021 20:48:26 +0100 Subject: [PATCH 08/11] Define profile variable to show user connected - OPID_PROFILE_SCREEN_PROVIDER is a variable to show profile of user connected. You can defined your own provider or use classic provider of WorkAdventure. - OpenIdProfileController with url "/profile" get user data and create simple html to show user informations. This url will be called with params 'accessToken' - If you define your custom profile url, it will be called with param 'accessToken'. accessToken is token to access at user informations in your OpenId provider. --- .env.template | 3 +- docker-compose.single-domain.yaml | 1 + docker-compose.yaml | 1 + pusher/src/App.ts | 3 + .../src/Controller/OpenIdProfileController.ts | 80 +++++++++++++++++++ pusher/src/Enum/EnvironmentVariable.ts | 1 + pusher/src/Services/AdminApi.ts | 12 +-- 7 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 pusher/src/Controller/OpenIdProfileController.ts diff --git a/.env.template b/.env.template index e7cdffbc..5328fe08 100644 --- a/.env.template +++ b/.env.template @@ -23,7 +23,8 @@ OPID_CLIENT_ID= OPID_CLIENT_SECRET= OPID_CLIENT_ISSUER= OPID_CLIENT_REDIREC_URL= -OPID_LOGIN_SCREEN_PROVIDER= +OPID_LOGIN_SCREEN_PROVIDER=http://pusher.workadventure.localhost/login-screen +OPID_PROFILE_SCREEN_PROVIDER= DISABLE_ANONYMOUS= # If you want to have a contact page in your menu, you MUST set CONTACT_URL to the URL of the page that you want diff --git a/docker-compose.single-domain.yaml b/docker-compose.single-domain.yaml index 4522cbe9..e241c108 100644 --- a/docker-compose.single-domain.yaml +++ b/docker-compose.single-domain.yaml @@ -72,6 +72,7 @@ services: OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER OPID_CLIENT_REDIREC_URL: $OPID_CLIENT_REDIREC_URL + OPID_PROFILE_SCREEN_PROVIDER: $OPID_PROFILE_SCREEN_PROVIDER DISABLE_ANONYMOUS: $DISABLE_ANONYMOUS volumes: - ./pusher:/usr/src/app diff --git a/docker-compose.yaml b/docker-compose.yaml index cc27a78a..03395f22 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -74,6 +74,7 @@ services: OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER OPID_CLIENT_REDIREC_URL: $OPID_CLIENT_REDIREC_URL + OPID_PROFILE_SCREEN_PROVIDER: $OPID_PROFILE_SCREEN_PROVIDER DISABLE_ANONYMOUS: $DISABLE_ANONYMOUS volumes: - ./pusher:/usr/src/app diff --git a/pusher/src/App.ts b/pusher/src/App.ts index 81aed045..327d493c 100644 --- a/pusher/src/App.ts +++ b/pusher/src/App.ts @@ -6,6 +6,7 @@ import { PrometheusController } from "./Controller/PrometheusController"; import { DebugController } from "./Controller/DebugController"; import { App as uwsApp } from "./Server/sifrr.server"; import { AdminController } from "./Controller/AdminController"; +import { OpenIdProfileController } from "./Controller/OpenIdProfileController"; class App { public app: uwsApp; @@ -15,6 +16,7 @@ class App { public prometheusController: PrometheusController; private debugController: DebugController; private adminController: AdminController; + private openIdProfileController: OpenIdProfileController; constructor() { this.app = new uwsApp(); @@ -26,6 +28,7 @@ class App { this.prometheusController = new PrometheusController(this.app); this.debugController = new DebugController(this.app); this.adminController = new AdminController(this.app); + this.openIdProfileController = new OpenIdProfileController(this.app); } } diff --git a/pusher/src/Controller/OpenIdProfileController.ts b/pusher/src/Controller/OpenIdProfileController.ts new file mode 100644 index 00000000..372b603b --- /dev/null +++ b/pusher/src/Controller/OpenIdProfileController.ts @@ -0,0 +1,80 @@ +import { BaseController } from "./BaseController"; +import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js"; +import { parse } from "query-string"; +import { openIDClient } from "../Services/OpenIDClient"; +import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager"; +import { adminApi } from "../Services/AdminApi"; +import { OPID_CLIENT_ISSUER } from "../Enum/EnvironmentVariable"; +import { IntrospectionResponse } from "openid-client"; + +export class OpenIdProfileController extends BaseController { + constructor(private App: TemplatedApp) { + super(); + this.profileOpenId(); + } + + profileOpenId() { + //eslint-disable-next-line @typescript-eslint/no-misused-promises + this.App.get("/profile", async (res: HttpResponse, req: HttpRequest) => { + res.onAborted(() => { + console.warn("/message request was aborted"); + }); + + const { accessToken } = parse(req.getQuery()); + if (!accessToken) { + throw Error("Access token expected cannot to be check on Hydra"); + } + try { + const resCheckTokenAuth = await openIDClient.checkTokenAuth(accessToken as string); + if (!resCheckTokenAuth.email) { + throw "Email was not found"; + } + res.end( + this.buildHtml( + OPID_CLIENT_ISSUER, + resCheckTokenAuth.email as string, + resCheckTokenAuth.picture as string | undefined + ) + ); + } catch (error) { + console.error("profileCallback => ERROR", error); + this.errorToResponse(error, res); + } + }); + } + + buildHtml(domain: string, email: string, pictureUrl?: string) { + return ( + "" + + ` +
+ +
+ +
+
+ +
+
+ Profile validated by domain: ${domain} +
+
+ Your email: ${email} +
+
+ + ` + ); + } +} diff --git a/pusher/src/Enum/EnvironmentVariable.ts b/pusher/src/Enum/EnvironmentVariable.ts index 43bfc7bf..52382266 100644 --- a/pusher/src/Enum/EnvironmentVariable.ts +++ b/pusher/src/Enum/EnvironmentVariable.ts @@ -16,6 +16,7 @@ export const OPID_CLIENT_ID = process.env.OPID_CLIENT_ID || ""; export const OPID_CLIENT_SECRET = process.env.OPID_CLIENT_SECRET || ""; export const OPID_CLIENT_ISSUER = process.env.OPID_CLIENT_ISSUER || ""; export const OPID_CLIENT_REDIREC_URL = process.env.OPID_CLIENT_REDIREC_URL || FRONT_URL + "/jwt"; +export const OPID_PROFILE_SCREEN_PROVIDER = process.env.OPID_PROFILE_SCREEN_PROVIDER || ADMIN_URL + "/profile"; export const DISABLE_ANONYMOUS = process.env.DISABLE_ANONYMOUS || false; export { diff --git a/pusher/src/Services/AdminApi.ts b/pusher/src/Services/AdminApi.ts index d002ff8b..6e1848eb 100644 --- a/pusher/src/Services/AdminApi.ts +++ b/pusher/src/Services/AdminApi.ts @@ -1,4 +1,4 @@ -import { ADMIN_API_TOKEN, ADMIN_API_URL, ADMIN_URL } from "../Enum/EnvironmentVariable"; +import { ADMIN_API_TOKEN, ADMIN_API_URL, ADMIN_URL, OPID_PROFILE_SCREEN_PROVIDER } from "../Enum/EnvironmentVariable"; import Axios from "axios"; import { GameRoomPolicyTypes } from "_Model/PusherRoom"; import { CharacterTexture } from "./AdminApi/CharacterTexture"; @@ -142,13 +142,15 @@ class AdminApi { }); } - /*TODO add constant to use profile companny*/ + /** + * + * @param accessToken + */ getProfileUrl(accessToken: string): string { - if (!ADMIN_URL) { + if (!OPID_PROFILE_SCREEN_PROVIDER) { throw new Error("No admin backoffice set!"); } - - return ADMIN_URL + `/profile?token=${accessToken}`; + return `${OPID_PROFILE_SCREEN_PROVIDER}?accessToken=${accessToken}`; } async logoutOauth(token: string) { From 16c08d86f29e144bf995b72b136d48fc5f41c5ba Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Mon, 15 Nov 2021 12:30:25 +0100 Subject: [PATCH 09/11] Update hydraAccessToken to accessToken --- pusher/src/Controller/AuthenticateController.ts | 15 ++++++++------- pusher/src/Services/JWTTokenManager.ts | 6 +++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/pusher/src/Controller/AuthenticateController.ts b/pusher/src/Controller/AuthenticateController.ts index 2dafe065..7b1f50bd 100644 --- a/pusher/src/Controller/AuthenticateController.ts +++ b/pusher/src/Controller/AuthenticateController.ts @@ -62,10 +62,11 @@ export class AuthenticateController extends BaseController { if (token != undefined) { try { const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false); - if (authTokenData.hydraAccessToken == undefined) { + if (authTokenData.accessToken == undefined) { throw Error("Token cannot to be check on Hydra"); } - await openIDClient.checkTokenAuth(authTokenData.hydraAccessToken); + const resCheckTokenAuth = await openIDClient.checkTokenAuth(authTokenData.accessToken); + console.log("resCheckTokenAuth", resCheckTokenAuth); res.writeStatus("200"); this.addCorsHeaders(res); return res.end(JSON.stringify({ authToken: token })); @@ -100,10 +101,10 @@ export class AuthenticateController extends BaseController { try { const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false); - if (authTokenData.hydraAccessToken == undefined) { + if (authTokenData.accessToken == undefined) { throw Error("Token cannot to be logout on Hydra"); } - await openIDClient.logoutUser(authTokenData.hydraAccessToken); + await openIDClient.logoutUser(authTokenData.accessToken); } catch (error) { console.error("openIDCallback => logout-callback", error); } finally { @@ -208,14 +209,14 @@ export class AuthenticateController extends BaseController { if (token != undefined) { try { const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false); - if (authTokenData.hydraAccessToken == undefined) { + if (authTokenData.accessToken == undefined) { throw Error("Token cannot to be check on Hydra"); } - await openIDClient.checkTokenAuth(authTokenData.hydraAccessToken); + await openIDClient.checkTokenAuth(authTokenData.accessToken); //get login profile res.writeStatus("302"); - res.writeHeader("Location", adminApi.getProfileUrl(authTokenData.hydraAccessToken)); + res.writeHeader("Location", adminApi.getProfileUrl(authTokenData.accessToken)); this.addCorsHeaders(res); // eslint-disable-next-line no-unsafe-finally return res.end(); diff --git a/pusher/src/Services/JWTTokenManager.ts b/pusher/src/Services/JWTTokenManager.ts index 24393084..2f482dbf 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; + accessToken?: string; } export const tokenInvalidException = "tokenInvalid"; class JWTTokenManager { - public createAuthToken(identifier: string, hydraAccessToken?: string) { - return Jwt.sign({ identifier, hydraAccessToken }, SECRET_KEY, { expiresIn: "30d" }); + public createAuthToken(identifier: string, accessToken?: string) { + return Jwt.sign({ identifier, accessToken }, SECRET_KEY, { expiresIn: "30d" }); } public verifyJWTToken(token: string, ignoreExpiration: boolean = false): AuthTokenData { From 61b8d584af01432deca9b4d014c5b97f3e086e2e Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Mon, 15 Nov 2021 14:34:23 +0100 Subject: [PATCH 10/11] delete useless userIdentify --- pusher/src/Controller/AuthenticateController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pusher/src/Controller/AuthenticateController.ts b/pusher/src/Controller/AuthenticateController.ts index 7b1f50bd..0cef24bb 100644 --- a/pusher/src/Controller/AuthenticateController.ts +++ b/pusher/src/Controller/AuthenticateController.ts @@ -203,7 +203,7 @@ export class AuthenticateController extends BaseController { res.onAborted(() => { console.warn("/message request was aborted"); }); - const { userIdentify, token } = parse(req.getQuery()); + const { token } = parse(req.getQuery()); try { //verify connected by token if (token != undefined) { From bbc2ac255002133b00607b0cb8d6e73bc0c6dfc9 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Mon, 15 Nov 2021 15:25:49 +0100 Subject: [PATCH 11/11] Update Connection manager to clean url history of navigator --- front/src/Connexion/ConnectionManager.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index 1f844bf2..793831bf 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -212,6 +212,8 @@ class ConnectionManager { analyticsClient.identifyUser(this.localUser.uuid, this.localUser.email); } + //clean history with new URL + window.history.pushState({}, document.title, window.location.pathname); this.serviceWorker = new _ServiceWorker(); return Promise.resolve(this._currentRoom); }