Merge pull request #1553 from thecodingmachine/openidAdminConnect
OpenId & Admin connect
This commit is contained in:
commit
8b6c16fd30
@ -22,6 +22,10 @@ MAX_USERNAME_LENGTH=8
|
|||||||
OPID_CLIENT_ID=
|
OPID_CLIENT_ID=
|
||||||
OPID_CLIENT_SECRET=
|
OPID_CLIENT_SECRET=
|
||||||
OPID_CLIENT_ISSUER=
|
OPID_CLIENT_ISSUER=
|
||||||
|
OPID_CLIENT_REDIRECT_URL=
|
||||||
|
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
|
# 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=
|
CONTACT_URL=
|
@ -40,6 +40,7 @@ services:
|
|||||||
TURN_USER: ""
|
TURN_USER: ""
|
||||||
TURN_PASSWORD: ""
|
TURN_PASSWORD: ""
|
||||||
START_ROOM_URL: "$START_ROOM_URL"
|
START_ROOM_URL: "$START_ROOM_URL"
|
||||||
|
DISABLE_ANONYMOUS: "$DISABLE_ANONYMOUS"
|
||||||
command: yarn run start
|
command: yarn run start
|
||||||
volumes:
|
volumes:
|
||||||
- ./front:/usr/src/app
|
- ./front:/usr/src/app
|
||||||
@ -70,6 +71,9 @@ services:
|
|||||||
OPID_CLIENT_ID: $OPID_CLIENT_ID
|
OPID_CLIENT_ID: $OPID_CLIENT_ID
|
||||||
OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET
|
OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET
|
||||||
OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER
|
OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER
|
||||||
|
OPID_CLIENT_REDIRECT_URL: $OPID_CLIENT_REDIRECT_URL
|
||||||
|
OPID_PROFILE_SCREEN_PROVIDER: $OPID_PROFILE_SCREEN_PROVIDER
|
||||||
|
DISABLE_ANONYMOUS: $DISABLE_ANONYMOUS
|
||||||
volumes:
|
volumes:
|
||||||
- ./pusher:/usr/src/app
|
- ./pusher:/usr/src/app
|
||||||
labels:
|
labels:
|
||||||
|
@ -43,6 +43,8 @@ services:
|
|||||||
START_ROOM_URL: "$START_ROOM_URL"
|
START_ROOM_URL: "$START_ROOM_URL"
|
||||||
MAX_PER_GROUP: "$MAX_PER_GROUP"
|
MAX_PER_GROUP: "$MAX_PER_GROUP"
|
||||||
MAX_USERNAME_LENGTH: "$MAX_USERNAME_LENGTH"
|
MAX_USERNAME_LENGTH: "$MAX_USERNAME_LENGTH"
|
||||||
|
DISABLE_ANONYMOUS: "$DISABLE_ANONYMOUS"
|
||||||
|
OPID_LOGIN_SCREEN_PROVIDER: "$OPID_LOGIN_SCREEN_PROVIDER"
|
||||||
command: yarn run start
|
command: yarn run start
|
||||||
volumes:
|
volumes:
|
||||||
- ./front:/usr/src/app
|
- ./front:/usr/src/app
|
||||||
@ -71,6 +73,9 @@ services:
|
|||||||
OPID_CLIENT_ID: $OPID_CLIENT_ID
|
OPID_CLIENT_ID: $OPID_CLIENT_ID
|
||||||
OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET
|
OPID_CLIENT_SECRET: $OPID_CLIENT_SECRET
|
||||||
OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER
|
OPID_CLIENT_ISSUER: $OPID_CLIENT_ISSUER
|
||||||
|
OPID_CLIENT_REDIRECT_URL: $OPID_CLIENT_REDIRECT_URL
|
||||||
|
OPID_PROFILE_SCREEN_PROVIDER: $OPID_PROFILE_SCREEN_PROVIDER
|
||||||
|
DISABLE_ANONYMOUS: $DISABLE_ANONYMOUS
|
||||||
volumes:
|
volumes:
|
||||||
- ./pusher:/usr/src/app
|
- ./pusher:/usr/src/app
|
||||||
labels:
|
labels:
|
||||||
|
@ -1,10 +1,60 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
||||||
|
function goToGettingStarted() {
|
||||||
|
const sparkHost = "https://workadventu.re/getting-started";
|
||||||
|
window.open(sparkHost, "_blank");
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToBuildingMap() {
|
||||||
|
const sparkHost = "https://workadventu.re/map-building/";
|
||||||
|
window.open(sparkHost, "_blank");
|
||||||
|
}
|
||||||
|
|
||||||
import {contactPageStore} from "../../Stores/MenuStore";
|
import {contactPageStore} from "../../Stores/MenuStore";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<iframe title="contact" src="{$contactPageStore}" allow="clipboard-read; clipboard-write self {$contactPageStore}" allowfullscreen></iframe>
|
<div class="create-map-main">
|
||||||
|
<section class="container-overflow">
|
||||||
|
<section>
|
||||||
|
<h3>Getting started</h3>
|
||||||
|
<p>
|
||||||
|
WorkAdventure allows you to create an online space to communicate spontaneously with others.
|
||||||
|
And it all starts with creating your own space. Choose from a large selection of prefabricated maps by our team.
|
||||||
|
</p>
|
||||||
|
<button type="button" class="nes-btn is-primary" on:click={goToGettingStarted}>Getting started</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h3>Create your map</h3>
|
||||||
|
<p>You can also create your own custom map by following the step of the documentation.</p>
|
||||||
|
<button type="button" class="nes-btn" on:click={goToBuildingMap}>Create your map</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<iframe title="contact"
|
||||||
|
src="{$contactPageStore}"
|
||||||
|
allow="clipboard-read; clipboard-write self {$contactPageStore}"
|
||||||
|
allowfullscreen></iframe>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
div.create-map-main {
|
||||||
|
height: calc(100% - 56px);
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
section {
|
||||||
|
margin-bottom: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.container-overflow {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
iframe {
|
iframe {
|
||||||
border: none;
|
border: none;
|
||||||
height: calc(100% - 56px);
|
height: calc(100% - 56px);
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
|
|
||||||
function goToGettingStarted() {
|
|
||||||
const sparkHost = "https://workadventu.re/getting-started";
|
|
||||||
window.open(sparkHost, "_blank");
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToBuildingMap() {
|
|
||||||
const sparkHost = "https://workadventu.re/map-building/";
|
|
||||||
window.open(sparkHost, "_blank");
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="create-map-main">
|
|
||||||
<section class="container-overflow">
|
|
||||||
<section>
|
|
||||||
<h3>Getting started</h3>
|
|
||||||
<p>
|
|
||||||
WorkAdventure allows you to create an online space to communicate spontaneously with others.
|
|
||||||
And it all starts with creating your own space. Choose from a large selection of prefabricated maps by our team.
|
|
||||||
</p>
|
|
||||||
<button type="button" class="nes-btn is-primary" on:click={goToGettingStarted}>Getting started</button>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h3>Create your map</h3>
|
|
||||||
<p>You can also create your own custom map by following the step of the documentation.</p>
|
|
||||||
<button type="button" class="nes-btn" on:click={goToBuildingMap}>Create your map</button>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
div.create-map-main {
|
|
||||||
height: calc(100% - 56px);
|
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
section {
|
|
||||||
margin-bottom: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.container-overflow {
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
75
front/src/Components/Menu/GuestSubMenu.svelte
Normal file
75
front/src/Components/Menu/GuestSubMenu.svelte
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let HTMLShareLink: HTMLInputElement;
|
||||||
|
|
||||||
|
function copyLink() {
|
||||||
|
HTMLShareLink.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function shareLink() {
|
||||||
|
const shareData = {url: location.toString()};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.share(shareData);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error: ' + err);
|
||||||
|
copyLink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="guest-main">
|
||||||
|
<section class="container-overflow">
|
||||||
|
<section class="share-url not-mobile">
|
||||||
|
<h3>Share the link of the room !</h3>
|
||||||
|
<input type="text" readonly bind:this={HTMLShareLink} value={location.toString()}>
|
||||||
|
<button type="button" class="nes-btn is-primary" on:click={copyLink}>Copy</button>
|
||||||
|
</section>
|
||||||
|
<section class="is-mobile">
|
||||||
|
<h3>Share the link of the room !</h3>
|
||||||
|
<input type="hidden" readonly bind:this={HTMLShareLink} value={location.toString()}>
|
||||||
|
<button type="button" class="nes-btn is-primary" on:click={shareLink}>Share</button>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
div.guest-main {
|
||||||
|
height: calc(100% - 56px);
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
section {
|
||||||
|
margin-bottom: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.container-overflow {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.is-mobile {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 900px), only screen and (max-height: 600px) {
|
||||||
|
div.guest-main {
|
||||||
|
section.share-url.not-mobile {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.is-mobile {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.container-overflow {
|
||||||
|
height: calc(100% - 120px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -2,11 +2,11 @@
|
|||||||
import {fly} from "svelte/transition";
|
import {fly} from "svelte/transition";
|
||||||
import SettingsSubMenu from "./SettingsSubMenu.svelte";
|
import SettingsSubMenu from "./SettingsSubMenu.svelte";
|
||||||
import ProfileSubMenu from "./ProfileSubMenu.svelte";
|
import ProfileSubMenu from "./ProfileSubMenu.svelte";
|
||||||
import CreateMapSubMenu from "./CreateMapSubMenu.svelte";
|
|
||||||
import AboutRoomSubMenu from "./AboutRoomSubMenu.svelte";
|
import AboutRoomSubMenu from "./AboutRoomSubMenu.svelte";
|
||||||
import GlobalMessageSubMenu from "./GlobalMessagesSubMenu.svelte";
|
import GlobalMessageSubMenu from "./GlobalMessagesSubMenu.svelte";
|
||||||
import ContactSubMenu from "./ContactSubMenu.svelte";
|
import ContactSubMenu from "./ContactSubMenu.svelte";
|
||||||
import CustomSubMenu from "./CustomSubMenu.svelte"
|
import CustomSubMenu from "./CustomSubMenu.svelte"
|
||||||
|
import GuestSubMenu from "./GuestSubMenu.svelte";
|
||||||
import {
|
import {
|
||||||
checkSubMenuToShow,
|
checkSubMenuToShow,
|
||||||
customMenuIframe,
|
customMenuIframe,
|
||||||
@ -19,21 +19,21 @@
|
|||||||
import type {Unsubscriber} from "svelte/store";
|
import type {Unsubscriber} from "svelte/store";
|
||||||
import {sendMenuClickedEvent} from "../../Api/iframe/Ui/MenuItem";
|
import {sendMenuClickedEvent} from "../../Api/iframe/Ui/MenuItem";
|
||||||
|
|
||||||
let activeSubMenu: string = SubMenusInterface.settings;
|
let activeSubMenu: string = SubMenusInterface.profile;
|
||||||
let activeComponent: typeof SettingsSubMenu | typeof CustomSubMenu = SettingsSubMenu;
|
let activeComponent: typeof ProfileSubMenu | typeof CustomSubMenu = ProfileSubMenu;
|
||||||
let props: { url: string, allowApi: boolean };
|
let props: { url: string, allowApi: boolean };
|
||||||
let unsubscriberSubMenuStore: Unsubscriber;
|
let unsubscriberSubMenuStore: Unsubscriber;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
unsubscriberSubMenuStore = subMenusStore.subscribe(() => {
|
unsubscriberSubMenuStore = subMenusStore.subscribe(() => {
|
||||||
if(!get(subMenusStore).includes(activeSubMenu)) {
|
if(!get(subMenusStore).includes(activeSubMenu)) {
|
||||||
switchMenu(SubMenusInterface.settings);
|
switchMenu(SubMenusInterface.profile);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
checkSubMenuToShow();
|
checkSubMenuToShow();
|
||||||
|
|
||||||
switchMenu(SubMenusInterface.settings);
|
switchMenu(SubMenusInterface.profile);
|
||||||
})
|
})
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
@ -52,8 +52,8 @@
|
|||||||
case SubMenusInterface.profile:
|
case SubMenusInterface.profile:
|
||||||
activeComponent = ProfileSubMenu;
|
activeComponent = ProfileSubMenu;
|
||||||
break;
|
break;
|
||||||
case SubMenusInterface.createMap:
|
case SubMenusInterface.invite:
|
||||||
activeComponent = CreateMapSubMenu;
|
activeComponent = GuestSubMenu;
|
||||||
break;
|
break;
|
||||||
case SubMenusInterface.aboutRoom:
|
case SubMenusInterface.aboutRoom:
|
||||||
activeComponent = AboutRoomSubMenu;
|
activeComponent = AboutRoomSubMenu;
|
||||||
|
@ -12,6 +12,10 @@
|
|||||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
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 btnProfileSubMenuIdentity from "../images/btn-menu-profile-identity.svg";
|
||||||
|
import btnProfileSubMenuCompanion from "../images/btn-menu-profile-companion.svg";
|
||||||
|
import btnProfileSubMenuWoka from "../images/btn-menu-profile-woka.svg";
|
||||||
|
|
||||||
|
|
||||||
function disableMenuStores(){
|
function disableMenuStores(){
|
||||||
@ -55,6 +59,28 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="customize-main">
|
<div class="customize-main">
|
||||||
|
<div class="submenu">
|
||||||
|
<section>
|
||||||
|
<button type="button" class="nes-btn" on:click|preventDefault={openEditNameScene}>
|
||||||
|
<img src={btnProfileSubMenuIdentity} alt="Edit your name">
|
||||||
|
<span class="btn-hover">Edit your name</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="nes-btn" on:click|preventDefault={openEditSkinScene}>
|
||||||
|
<img src={btnProfileSubMenuWoka} alt="Edit your WOKA">
|
||||||
|
<span class="btn-hover">Edit your WOKA</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="nes-btn" on:click|preventDefault={openEditCompanionScene}>
|
||||||
|
<img src={btnProfileSubMenuCompanion} alt="Edit your companion">
|
||||||
|
<span class="btn-hover">Edit your companion</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="nes-btn" on:click|preventDefault={openEnableCameraScene}>
|
||||||
|
<img src={btnProfileSubMenuCamera} alt="Edit your camera">
|
||||||
|
<span class="btn-hover">Edit your camera</span>
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
{#if $userIsConnected}
|
{#if $userIsConnected}
|
||||||
<section>
|
<section>
|
||||||
{#if PROFILE_URL != undefined}
|
{#if PROFILE_URL != undefined}
|
||||||
@ -69,18 +95,48 @@
|
|||||||
<a type="button" class="nes-btn" href="/login">Sign in</a>
|
<a type="button" class="nes-btn" href="/login">Sign in</a>
|
||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
<section>
|
</div>
|
||||||
<button type="button" class="nes-btn" on:click|preventDefault={openEditNameScene}>Edit Name</button>
|
|
||||||
<button type="button" class="nes-btn" on:click|preventDefault={openEditSkinScene}>Edit Skin</button>
|
|
||||||
<button type="button" class="nes-btn" on:click|preventDefault={openEditCompanionScene}>Edit Companion</button>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<button type="button" class="nes-btn" on:click|preventDefault={openEnableCameraScene}>Setup camera</button>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
div.customize-main{
|
div.customize-main{
|
||||||
|
width: 100%;
|
||||||
|
display: inline-flex;
|
||||||
|
|
||||||
|
div.submenu{
|
||||||
|
height: 100%;
|
||||||
|
width: 50px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
transition: all .5s ease;
|
||||||
|
text-align: left;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
max-height: 44px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 26px;
|
||||||
|
width: 26px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.btn-hover{
|
||||||
|
display: none;
|
||||||
|
font-family: "Press Start 2P";
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover{
|
||||||
|
width: auto;
|
||||||
|
|
||||||
|
span.btn-hover {
|
||||||
|
display: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.content {
|
||||||
|
width: 100%;
|
||||||
section {
|
section {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -88,7 +144,7 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
|
||||||
iframe{
|
iframe {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 50vh;
|
height: 50vh;
|
||||||
border: none;
|
border: none;
|
||||||
@ -100,9 +156,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 800px) {
|
@media only screen and (max-width: 800px) {
|
||||||
div.customize-main section button {
|
div.customize-main.content section button {
|
||||||
width: 130px;
|
width: 130px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1
front/src/Components/images/btn-menu-profile-camera.svg
Normal file
1
front/src/Components/images/btn-menu-profile-camera.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><g id="btn_setup_camera" data-name="btn setup camera"><circle cx="24.71" cy="13.11" r="1.46"/><path d="M8.65,23.34H40.78V2.89H31.16L28.24,0h-7L18.27,2.89H8.65ZM32,8.73h2.92v2.92H32Zm-7.31,0a4.39,4.39,0,1,1-4.38,4.38A4.39,4.39,0,0,1,24.71,8.73Z"/><path d="M2.81,44H5.73v5.84h8.76V44h5.84V46.9h2.92V44h5.84V46.9H32V44h5.84V46.9h2.92V44h5.84V41.06h-33l-3.52-3.53L6.58,41.06H2.81Z"/><path d="M2.81,32.3H8.65v2.92h2.92V32.3h5.84v2.92h2.92V32.3h5.84v2.92h2.92V32.3h5.85v5.84H43.7V32.3h2.92V29.37H42.84l-3.52-3.52-3.53,3.52h-33Z"/></g></svg>
|
After Width: | Height: | Size: 629 B |
@ -0,0 +1 @@
|
|||||||
|
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 50 50"><g id="btn_setup_companion" data-name="btn setup companion"><g id="iNhyGC.tif"><image id="Layer_0" data-name="Layer 0" width="15" height="30" transform="translate(13.21 0.25) scale(1.65)" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAfCAYAAADupU20AAAACXBIWXMAAAbEAAAGxAGo1xHEAAAB20lEQVQ4T51UO04DMRScRYmEhGWlApRsBcUap4VUXICSjpqaY3ACSgpOgERQKnpoNjXRhjrkANZGikRhiuDH8w8BI7l5n/H4fVxYaxFC66FnnM1eiyjoC53QoPXQjkYnkS1HUnAFWg+tUhXatsVi8Q4AUKqClBJ1PU0q8RTMZq+Fk++CU4o4oiekbjHGhCbCVtbzS/xIwGuSQ/GbNgL5VkYEWg9tWQ4ghCCb60qKJGqjS5ZSkt0YkyWhGjjZYTIASClJUfg8r4hKVWiaOYwx0WmaOZSqEKIDfEu/PDvGHYCmmUeBSlW4PDvGTdt6o00Krq8uAIDkl+UASlUoy4Fnd3EOHXc7AOzu7QN4o/l3ie79G/+G3Kkojo50svI5hB3pAOnK5xDG0TLV9ZSMqe3jft4NKuLt/Qu6qzXG40m0fcYYjMcTdFdr3N6/eL4tYDOqhwc9AMDT4wNScPbDg563XFTEfr8P+3C6MZ4/RwTct1wuqYiFtTa5A3U9RXe1xsfONtUktRNUxMXi3Rua0egExhgi5MkcRFCWA3I6IgD0Xu7jJEQghKDE8BaX6OI4vE9VCEE38r3nqx7CW2c+9yFy00oE/x1lInDTlyPi3eCgP5F/VanPM+dPfut/wScEgxZt/KHdLgAAAABJRU5ErkJggg=="/></g></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1 @@
|
|||||||
|
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><g id="btn_setup_identity" data-name="btn setup identity"><path d="M31.94,6l-4.35,6.39a3.63,3.63,0,1,1-3.07-1.7,3.71,3.71,0,0,1,.67.06l4-5.89a16,16,0,0,0-9.37,0L17,.76a1.45,1.45,0,0,0-2.4,1.64L17.09,6A16,16,0,0,0,8.56,20.15V33.69a16,16,0,0,0,31.91,0V20.15A16,16,0,0,0,31.94,6Zm-.65,32.49H17.75a1.45,1.45,0,1,1,0-2.9H31.29a1.45,1.45,0,0,1,0,2.9Zm0-5.8H17.75a1.45,1.45,0,1,1,0-2.9H31.29a1.45,1.45,0,1,1,0,2.9Zm0-5.8H17.75a1.45,1.45,0,1,1,0-2.9H31.29a1.45,1.45,0,1,1,0,2.9Z"/><path d="M34.42,2.4A1.45,1.45,0,1,0,32,.76L29.21,4.89A16.38,16.38,0,0,1,31.94,6Z"/></g></svg>
|
After Width: | Height: | Size: 661 B |
1
front/src/Components/images/btn-menu-profile-woka.svg
Normal file
1
front/src/Components/images/btn-menu-profile-woka.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 50 50"><g id="btn_setup_woka" data-name="btn setup woka"><g id="NP8bMB.tif"><image id="Layer_0" data-name="Layer 0" width="23" height="29" transform="translate(4.61 0.1) scale(1.71)" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAdCAYAAABBsffGAAAACXBIWXMAAAZ1AAAGdQGEn07tAAACCUlEQVRIS61WS4rDMAx9abLoxuqi4MIYSk+Q+59iBnqCUlBhDNnYmy4aMosgj+3YyRTmQReVpSdZP6eZpglrMOajqsD8aGpnANDUyIW0bTsAwPF4DGfDMAAAxvEFoO6kSG7Mx9S2XSBUSi10vPcAZkfj+Co6WJDHxCXSHN77qoOuZJATE9FCxzkH4PdW1n4vdHbxH4k6Rom4JG/bblH8hBxIo64RC+RcKZUUXLAgF2wRC9b0AvlaP7+DmGcnAq1PdYs3oPUpOKim5T+wA+YJk1aS6XsXYmftd+j3EPnWnvgrYp4kLcyPZhxf8N6HIdmCcw7e+79PaDxta60mxKXpBLLdEk/oOL6wtmPinRLbxNEHciHu+34zohhan6CUAhHher0mDpK0xMQSrTEGAJIaSKqYGUqpsH77vsfX12fQ62aCOeo8YmMMDodDUHbOgYgSGTMnN5QFxvxoQrdcLhcA633+fD6rZ2InPADQSdREBGYOT1fuxFqL/X4Pa22QMXPy5HnvYYwJ0e+AOVc5oXSJcw7OOWitQUTQWgdZvmrFXvhCWpxzSaWHYQiFk6GSnxSQiAKhDGBc+I750cgWK60AIsLtdlukSbooRs5V/bQ4n8+TpCbveeltGaT7/V7cS9WVK4WVFADpzUQueiWs7nOlFIwxi/GvyXOskm+9o1vn1ZwD6XsYp6Qmz/ED0qpw9h1b2uQAAAAASUVORK5CYII="/></g></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -41,7 +41,6 @@ class ConnectionManager {
|
|||||||
const nonce = localUserStore.generateNonce();
|
const nonce = localUserStore.generateNonce();
|
||||||
localUserStore.setAuthToken(null);
|
localUserStore.setAuthToken(null);
|
||||||
|
|
||||||
//TODO fix me to redirect this URL by pusher
|
|
||||||
if (!this._currentRoom || !this._currentRoom.iframeAuthentication) {
|
if (!this._currentRoom || !this._currentRoom.iframeAuthentication) {
|
||||||
loginSceneVisibleIframeStore.set(false);
|
loginSceneVisibleIframeStore.set(false);
|
||||||
return null;
|
return null;
|
||||||
@ -79,6 +78,16 @@ class ConnectionManager {
|
|||||||
const connexionType = urlManager.getGameConnexionType();
|
const connexionType = urlManager.getGameConnexionType();
|
||||||
this.connexionType = connexionType;
|
this.connexionType = connexionType;
|
||||||
this._currentRoom = null;
|
this._currentRoom = null;
|
||||||
|
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const token = urlParams.get("token");
|
||||||
|
if (token) {
|
||||||
|
this.authToken = token;
|
||||||
|
localUserStore.setAuthToken(token);
|
||||||
|
//token was saved, clear url
|
||||||
|
urlParams.delete("token");
|
||||||
|
}
|
||||||
|
|
||||||
if (connexionType === GameConnexionTypes.login) {
|
if (connexionType === GameConnexionTypes.login) {
|
||||||
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
|
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
|
||||||
if (this.loadOpenIDScreen() !== null) {
|
if (this.loadOpenIDScreen() !== null) {
|
||||||
@ -87,6 +96,8 @@ class ConnectionManager {
|
|||||||
urlManager.pushRoomIdToUrl(this._currentRoom);
|
urlManager.pushRoomIdToUrl(this._currentRoom);
|
||||||
} else if (connexionType === GameConnexionTypes.jwt) {
|
} else if (connexionType === GameConnexionTypes.jwt) {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
const code = urlParams.get("code");
|
const code = urlParams.get("code");
|
||||||
const state = urlParams.get("state");
|
const state = urlParams.get("state");
|
||||||
if (!state || !localUserStore.verifyState(state)) {
|
if (!state || !localUserStore.verifyState(state)) {
|
||||||
@ -96,6 +107,8 @@ class ConnectionManager {
|
|||||||
throw "No Auth code provided";
|
throw "No Auth code provided";
|
||||||
}
|
}
|
||||||
localUserStore.setCode(code);
|
localUserStore.setCode(code);
|
||||||
|
}
|
||||||
|
|
||||||
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
|
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
|
||||||
try {
|
try {
|
||||||
await this.checkAuthUserConnexion();
|
await this.checkAuthUserConnexion();
|
||||||
@ -170,8 +183,11 @@ class ConnectionManager {
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await this.checkAuthUserConnexion();
|
await this.checkAuthUserConnexion();
|
||||||
|
analyticsClient.loggedWithSso();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
this.loadOpenIDScreen();
|
||||||
|
return Promise.reject(new Error("You will be redirect on login page"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.localUser = localUserStore.getLocalUser() as LocalUser; //if authToken exist in localStorage then localUser cannot be null
|
this.localUser = localUserStore.getLocalUser() as LocalUser; //if authToken exist in localStorage then localUser cannot be null
|
||||||
@ -199,6 +215,8 @@ class ConnectionManager {
|
|||||||
analyticsClient.identifyUser(this.localUser.uuid, this.localUser.email);
|
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();
|
this.serviceWorker = new _ServiceWorker();
|
||||||
return Promise.resolve(this._currentRoom);
|
return Promise.resolve(this._currentRoom);
|
||||||
}
|
}
|
||||||
@ -279,16 +297,19 @@ class ConnectionManager {
|
|||||||
//set connected store for menu at false
|
//set connected store for menu at false
|
||||||
userIsConnected.set(false);
|
userIsConnected.set(false);
|
||||||
|
|
||||||
|
const token = localUserStore.getAuthToken();
|
||||||
const state = localUserStore.getState();
|
const state = localUserStore.getState();
|
||||||
const code = localUserStore.getCode();
|
const code = localUserStore.getCode();
|
||||||
|
const nonce = localUserStore.getNonce();
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
if (!state || !localUserStore.verifyState(state)) {
|
if (!state || !localUserStore.verifyState(state)) {
|
||||||
throw "Could not validate state!";
|
throw "Could not validate state!";
|
||||||
}
|
}
|
||||||
if (!code) {
|
if (!code) {
|
||||||
throw "No Auth code provided";
|
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(
|
const { authToken } = await Axios.get(`${PUSHER_URL}/login-callback`, { params: { code, nonce, token } }).then(
|
||||||
(res) => res.data
|
(res) => res.data
|
||||||
);
|
);
|
||||||
|
@ -165,8 +165,15 @@ class LocalUserStore {
|
|||||||
|
|
||||||
verifyState(value: string): boolean {
|
verifyState(value: string): boolean {
|
||||||
const oldValue = localStorage.getItem(state);
|
const oldValue = localStorage.getItem(state);
|
||||||
|
if (!oldValue) {
|
||||||
|
localStorage.setItem(state, value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return oldValue === value;
|
return oldValue === value;
|
||||||
}
|
}
|
||||||
|
setState(value: string) {
|
||||||
|
localStorage.setItem(state, value);
|
||||||
|
}
|
||||||
getState(): string | null {
|
getState(): string | null {
|
||||||
return localStorage.getItem(state);
|
return localStorage.getItem(state);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import { CONTACT_URL, PUSHER_URL } from "../Enum/EnvironmentVariable";
|
import { CONTACT_URL, PUSHER_URL, DISABLE_ANONYMOUS, OPID_LOGIN_SCREEN_PROVIDER } from "../Enum/EnvironmentVariable";
|
||||||
import type { CharacterTexture } from "./LocalUser";
|
import type { CharacterTexture } from "./LocalUser";
|
||||||
import { localUserStore } from "./LocalUserStore";
|
import { localUserStore } from "./LocalUserStore";
|
||||||
|
|
||||||
@ -14,8 +14,8 @@ export interface RoomRedirect {
|
|||||||
export class Room {
|
export class Room {
|
||||||
public readonly id: string;
|
public readonly id: string;
|
||||||
public readonly isPublic: boolean;
|
public readonly isPublic: boolean;
|
||||||
private _authenticationMandatory: boolean = false;
|
private _authenticationMandatory: boolean = DISABLE_ANONYMOUS as boolean;
|
||||||
private _iframeAuthentication?: string;
|
private _iframeAuthentication?: string = OPID_LOGIN_SCREEN_PROVIDER;
|
||||||
private _mapUrl: string | undefined;
|
private _mapUrl: string | undefined;
|
||||||
private _textures: CharacterTexture[] | undefined;
|
private _textures: CharacterTexture[] | undefined;
|
||||||
private instance: string | undefined;
|
private instance: string | undefined;
|
||||||
@ -106,8 +106,8 @@ export class Room {
|
|||||||
this._mapUrl = data.mapUrl;
|
this._mapUrl = data.mapUrl;
|
||||||
this._textures = data.textures;
|
this._textures = data.textures;
|
||||||
this._group = data.group;
|
this._group = data.group;
|
||||||
this._authenticationMandatory = data.authenticationMandatory || false;
|
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;
|
this._contactPage = data.contactPage || CONTACT_URL;
|
||||||
return new MapDetail(data.mapUrl, data.textures);
|
return new MapDetail(data.mapUrl, data.textures);
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,8 @@ export const CONTACT_URL = process.env.CONTACT_URL || undefined;
|
|||||||
export const PROFILE_URL = process.env.PROFILE_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_API_KEY: string = (process.env.POSTHOG_API_KEY as string) || "";
|
||||||
export const POSTHOG_URL = process.env.POSTHOG_URL || undefined;
|
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;
|
export const isMobile = (): boolean => window.innerWidth <= 800 || window.innerHeight <= 600;
|
||||||
|
|
||||||
|
@ -34,20 +34,20 @@ export const warningContainerStore = createWarningContainerStore();
|
|||||||
export enum SubMenusInterface {
|
export enum SubMenusInterface {
|
||||||
settings = "Settings",
|
settings = "Settings",
|
||||||
profile = "Profile",
|
profile = "Profile",
|
||||||
createMap = "Create a Map",
|
invite = "Invite",
|
||||||
aboutRoom = "About the Room",
|
aboutRoom = "Credit",
|
||||||
globalMessages = "Global Messages",
|
globalMessages = "Global Messages",
|
||||||
contact = "Contact",
|
contact = "Contact",
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSubMenusStore() {
|
function createSubMenusStore() {
|
||||||
const { subscribe, update } = writable<string[]>([
|
const { subscribe, update } = writable<string[]>([
|
||||||
SubMenusInterface.settings,
|
|
||||||
SubMenusInterface.profile,
|
SubMenusInterface.profile,
|
||||||
SubMenusInterface.createMap,
|
|
||||||
SubMenusInterface.aboutRoom,
|
|
||||||
SubMenusInterface.globalMessages,
|
SubMenusInterface.globalMessages,
|
||||||
SubMenusInterface.contact,
|
SubMenusInterface.contact,
|
||||||
|
SubMenusInterface.settings,
|
||||||
|
SubMenusInterface.invite,
|
||||||
|
SubMenusInterface.aboutRoom,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -7,7 +7,6 @@ import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
|||||||
import sveltePreprocess from "svelte-preprocess";
|
import sveltePreprocess from "svelte-preprocess";
|
||||||
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
|
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
|
||||||
import NodePolyfillPlugin from "node-polyfill-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 mode = process.env.NODE_ENV ?? "development";
|
||||||
const buildNpmTypingsForApi = !!process.env.BUILD_TYPINGS;
|
const buildNpmTypingsForApi = !!process.env.BUILD_TYPINGS;
|
||||||
@ -208,6 +207,8 @@ module.exports = {
|
|||||||
POSTHOG_API_KEY: null,
|
POSTHOG_API_KEY: null,
|
||||||
POSTHOG_URL: null,
|
POSTHOG_URL: null,
|
||||||
NODE_ENV: mode,
|
NODE_ENV: mode,
|
||||||
|
DISABLE_ANONYMOUS: false,
|
||||||
|
OPID_LOGIN_SCREEN_PROVIDER: null,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
} as Configuration & WebpackDevServer.Configuration;
|
} as Configuration & WebpackDevServer.Configuration;
|
||||||
|
@ -6,6 +6,7 @@ import { PrometheusController } from "./Controller/PrometheusController";
|
|||||||
import { DebugController } from "./Controller/DebugController";
|
import { DebugController } from "./Controller/DebugController";
|
||||||
import { App as uwsApp } from "./Server/sifrr.server";
|
import { App as uwsApp } from "./Server/sifrr.server";
|
||||||
import { AdminController } from "./Controller/AdminController";
|
import { AdminController } from "./Controller/AdminController";
|
||||||
|
import { OpenIdProfileController } from "./Controller/OpenIdProfileController";
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
public app: uwsApp;
|
public app: uwsApp;
|
||||||
@ -15,6 +16,7 @@ class App {
|
|||||||
public prometheusController: PrometheusController;
|
public prometheusController: PrometheusController;
|
||||||
private debugController: DebugController;
|
private debugController: DebugController;
|
||||||
private adminController: AdminController;
|
private adminController: AdminController;
|
||||||
|
private openIdProfileController: OpenIdProfileController;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.app = new uwsApp();
|
this.app = new uwsApp();
|
||||||
@ -26,6 +28,7 @@ class App {
|
|||||||
this.prometheusController = new PrometheusController(this.app);
|
this.prometheusController = new PrometheusController(this.app);
|
||||||
this.debugController = new DebugController(this.app);
|
this.debugController = new DebugController(this.app);
|
||||||
this.adminController = new AdminController(this.app);
|
this.adminController = new AdminController(this.app);
|
||||||
|
this.openIdProfileController = new OpenIdProfileController(this.app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import { adminApi } 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";
|
||||||
|
import { DISABLE_ANONYMOUS } from "../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
export interface TokenInterface {
|
export interface TokenInterface {
|
||||||
userUuid: string;
|
userUuid: string;
|
||||||
@ -61,10 +62,10 @@ export class AuthenticateController extends BaseController {
|
|||||||
if (token != undefined) {
|
if (token != undefined) {
|
||||||
try {
|
try {
|
||||||
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
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");
|
throw Error("Token cannot to be check on Hydra");
|
||||||
}
|
}
|
||||||
await openIDClient.checkTokenAuth(authTokenData.hydraAccessToken);
|
const resCheckTokenAuth = await openIDClient.checkTokenAuth(authTokenData.accessToken);
|
||||||
res.writeStatus("200");
|
res.writeStatus("200");
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
return res.end(JSON.stringify({ authToken: token }));
|
return res.end(JSON.stringify({ authToken: token }));
|
||||||
@ -99,10 +100,10 @@ export class AuthenticateController extends BaseController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
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");
|
throw Error("Token cannot to be logout on Hydra");
|
||||||
}
|
}
|
||||||
await openIDClient.logoutUser(authTokenData.hydraAccessToken);
|
await openIDClient.logoutUser(authTokenData.accessToken);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("openIDCallback => logout-callback", error);
|
console.error("openIDCallback => logout-callback", error);
|
||||||
} finally {
|
} finally {
|
||||||
@ -175,6 +176,10 @@ export class AuthenticateController extends BaseController {
|
|||||||
console.warn("Login request was aborted");
|
console.warn("Login request was aborted");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (DISABLE_ANONYMOUS) {
|
||||||
|
res.writeStatus("403 FORBIDDEN");
|
||||||
|
res.end();
|
||||||
|
} else {
|
||||||
const userUuid = v4();
|
const userUuid = v4();
|
||||||
const authToken = jwtTokenManager.createAuthToken(userUuid);
|
const authToken = jwtTokenManager.createAuthToken(userUuid);
|
||||||
res.writeStatus("200 OK");
|
res.writeStatus("200 OK");
|
||||||
@ -185,6 +190,7 @@ export class AuthenticateController extends BaseController {
|
|||||||
userUuid,
|
userUuid,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,20 +202,20 @@ export class AuthenticateController extends BaseController {
|
|||||||
res.onAborted(() => {
|
res.onAborted(() => {
|
||||||
console.warn("/message request was aborted");
|
console.warn("/message request was aborted");
|
||||||
});
|
});
|
||||||
const { userIdentify, token } = parse(req.getQuery());
|
const { token } = parse(req.getQuery());
|
||||||
try {
|
try {
|
||||||
//verify connected by token
|
//verify connected by token
|
||||||
if (token != undefined) {
|
if (token != undefined) {
|
||||||
try {
|
try {
|
||||||
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
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");
|
throw Error("Token cannot to be check on Hydra");
|
||||||
}
|
}
|
||||||
await openIDClient.checkTokenAuth(authTokenData.hydraAccessToken);
|
await openIDClient.checkTokenAuth(authTokenData.accessToken);
|
||||||
|
|
||||||
//get login profile
|
//get login profile
|
||||||
res.writeStatus("302");
|
res.writeStatus("302");
|
||||||
res.writeHeader("Location", adminApi.getProfileUrl(authTokenData.hydraAccessToken));
|
res.writeHeader("Location", adminApi.getProfileUrl(authTokenData.accessToken));
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
// eslint-disable-next-line no-unsafe-finally
|
// eslint-disable-next-line no-unsafe-finally
|
||||||
return res.end();
|
return res.end();
|
||||||
|
@ -26,7 +26,7 @@ import { jwtTokenManager, tokenInvalidException } from "../Services/JWTTokenMana
|
|||||||
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
import { adminApi, FetchMemberDataByUuidResponse } from "../Services/AdminApi";
|
||||||
import { SocketManager, socketManager } from "../Services/SocketManager";
|
import { SocketManager, socketManager } from "../Services/SocketManager";
|
||||||
import { emitInBatch } from "../Services/IoSocketHelpers";
|
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 { Zone } from "_Model/Zone";
|
||||||
import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface";
|
import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface";
|
||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
@ -175,6 +175,11 @@ export class IoSocketController {
|
|||||||
|
|
||||||
const tokenData =
|
const tokenData =
|
||||||
token && typeof token === "string" ? jwtTokenManager.verifyJWTToken(token) : null;
|
token && typeof token === "string" ? jwtTokenManager.verifyJWTToken(token) : null;
|
||||||
|
|
||||||
|
if (DISABLE_ANONYMOUS && !tokenData) {
|
||||||
|
throw new Error("Expecting token");
|
||||||
|
}
|
||||||
|
|
||||||
const userIdentifier = tokenData ? tokenData.identifier : "";
|
const userIdentifier = tokenData ? tokenData.identifier : "";
|
||||||
|
|
||||||
let memberTags: string[] = [];
|
let memberTags: string[] = [];
|
||||||
|
@ -2,9 +2,9 @@ import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
|
|||||||
import { BaseController } from "./BaseController";
|
import { BaseController } from "./BaseController";
|
||||||
import { parse } from "query-string";
|
import { parse } from "query-string";
|
||||||
import { adminApi } from "../Services/AdminApi";
|
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 { GameRoomPolicyTypes } from "../Model/PusherRoom";
|
||||||
import { MapDetailsData } from "../Services/AdminApi/MapDetailsData";
|
import { isMapDetailsData, MapDetailsData } from "../Services/AdminApi/MapDetailsData";
|
||||||
import { socketManager } from "../Services/SocketManager";
|
import { socketManager } from "../Services/SocketManager";
|
||||||
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
@ -64,6 +64,7 @@ export class MapController extends BaseController {
|
|||||||
tags: [],
|
tags: [],
|
||||||
textures: [],
|
textures: [],
|
||||||
contactPage: undefined,
|
contactPage: undefined,
|
||||||
|
authenticationMandatory: DISABLE_ANONYMOUS,
|
||||||
} as MapDetailsData)
|
} as MapDetailsData)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -87,6 +88,10 @@ export class MapController extends BaseController {
|
|||||||
}
|
}
|
||||||
const mapDetails = await adminApi.fetchMapDetails(query.playUri as string, userId);
|
const mapDetails = await adminApi.fetchMapDetails(query.playUri as string, userId);
|
||||||
|
|
||||||
|
if (isMapDetailsData(mapDetails) && DISABLE_ANONYMOUS) {
|
||||||
|
mapDetails.authenticationMandatory = true;
|
||||||
|
}
|
||||||
|
|
||||||
res.writeStatus("200 OK");
|
res.writeStatus("200 OK");
|
||||||
this.addCorsHeaders(res);
|
this.addCorsHeaders(res);
|
||||||
res.end(JSON.stringify(mapDetails));
|
res.end(JSON.stringify(mapDetails));
|
||||||
|
80
pusher/src/Controller/OpenIdProfileController.ts
Normal file
80
pusher/src/Controller/OpenIdProfileController.ts
Normal file
@ -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 `
|
||||||
|
<!DOCTYPE>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
*{
|
||||||
|
font-family: PixelFont-7, monospace;
|
||||||
|
}
|
||||||
|
body{
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
section{
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<section>
|
||||||
|
<img src="${pictureUrl ? pictureUrl : "/images/profile"}">
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
Profile validated by domain: <span style="font-weight: bold">${domain}</span>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
Your email: <span style="font-weight: bold">${email}</span>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,9 @@ export const FRONT_URL = process.env.FRONT_URL || "http://localhost";
|
|||||||
export const OPID_CLIENT_ID = process.env.OPID_CLIENT_ID || "";
|
export const OPID_CLIENT_ID = process.env.OPID_CLIENT_ID || "";
|
||||||
export const OPID_CLIENT_SECRET = process.env.OPID_CLIENT_SECRET || "";
|
export const OPID_CLIENT_SECRET = process.env.OPID_CLIENT_SECRET || "";
|
||||||
export const OPID_CLIENT_ISSUER = process.env.OPID_CLIENT_ISSUER || "";
|
export const OPID_CLIENT_ISSUER = process.env.OPID_CLIENT_ISSUER || "";
|
||||||
|
export const OPID_CLIENT_REDIRECT_URL = process.env.OPID_CLIENT_REDIRECT_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 {
|
export {
|
||||||
SECRET_KEY,
|
SECRET_KEY,
|
||||||
|
@ -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 Axios from "axios";
|
||||||
import { GameRoomPolicyTypes } from "_Model/PusherRoom";
|
import { GameRoomPolicyTypes } from "_Model/PusherRoom";
|
||||||
import { CharacterTexture } from "./AdminApi/CharacterTexture";
|
import { CharacterTexture } from "./AdminApi/CharacterTexture";
|
||||||
@ -142,13 +142,19 @@ class AdminApi {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*TODO add constant to use profile companny*/
|
/**
|
||||||
|
*
|
||||||
|
* @param accessToken
|
||||||
|
*/
|
||||||
getProfileUrl(accessToken: string): string {
|
getProfileUrl(accessToken: string): string {
|
||||||
if (!ADMIN_URL) {
|
if (!OPID_PROFILE_SCREEN_PROVIDER) {
|
||||||
throw new Error("No admin backoffice set!");
|
throw new Error("No admin backoffice set!");
|
||||||
}
|
}
|
||||||
|
return `${OPID_PROFILE_SCREEN_PROVIDER}?accessToken=${accessToken}`;
|
||||||
|
}
|
||||||
|
|
||||||
return ADMIN_URL + `/profile?token=${accessToken}`;
|
async logoutOauth(token: string) {
|
||||||
|
await Axios.get(ADMIN_API_URL + `/oauth/logout?token=${token}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ export const isMapDetailsData = new tg.IsInterface()
|
|||||||
tags: tg.isArray(tg.isString),
|
tags: tg.isArray(tg.isString),
|
||||||
textures: tg.isArray(isCharacterTexture),
|
textures: tg.isArray(isCharacterTexture),
|
||||||
contactPage: tg.isUnion(tg.isString, tg.isUndefined),
|
contactPage: tg.isUnion(tg.isString, tg.isUndefined),
|
||||||
|
authenticationMandatory: tg.isUnion(tg.isBoolean, tg.isUndefined),
|
||||||
})
|
})
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
|
@ -6,13 +6,13 @@ import { adminApi, AdminBannedData } from "../Services/AdminApi";
|
|||||||
|
|
||||||
export interface AuthTokenData {
|
export interface AuthTokenData {
|
||||||
identifier: string; //will be a email if logged in or an uuid if anonymous
|
identifier: string; //will be a email if logged in or an uuid if anonymous
|
||||||
hydraAccessToken?: string;
|
accessToken?: string;
|
||||||
}
|
}
|
||||||
export const tokenInvalidException = "tokenInvalid";
|
export const tokenInvalidException = "tokenInvalid";
|
||||||
|
|
||||||
class JWTTokenManager {
|
class JWTTokenManager {
|
||||||
public createAuthToken(identifier: string, hydraAccessToken?: string) {
|
public createAuthToken(identifier: string, accessToken?: string) {
|
||||||
return Jwt.sign({ identifier, hydraAccessToken }, SECRET_KEY, { expiresIn: "30d" });
|
return Jwt.sign({ identifier, accessToken }, SECRET_KEY, { expiresIn: "30d" });
|
||||||
}
|
}
|
||||||
|
|
||||||
public verifyJWTToken(token: string, ignoreExpiration: boolean = false): AuthTokenData {
|
public verifyJWTToken(token: string, ignoreExpiration: boolean = false): AuthTokenData {
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import { Issuer, Client, IntrospectionResponse } 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";
|
import {
|
||||||
|
OPID_CLIENT_ID,
|
||||||
const opidRedirectUri = FRONT_URL + "/jwt";
|
OPID_CLIENT_SECRET,
|
||||||
|
OPID_CLIENT_ISSUER,
|
||||||
|
OPID_CLIENT_REDIRECT_URL,
|
||||||
|
} from "../Enum/EnvironmentVariable";
|
||||||
|
|
||||||
class OpenIDClient {
|
class OpenIDClient {
|
||||||
private issuerPromise: Promise<Client> | null = null;
|
private issuerPromise: Promise<Client> | null = null;
|
||||||
@ -12,7 +15,7 @@ class OpenIDClient {
|
|||||||
return new issuer.Client({
|
return new issuer.Client({
|
||||||
client_id: OPID_CLIENT_ID,
|
client_id: OPID_CLIENT_ID,
|
||||||
client_secret: OPID_CLIENT_SECRET,
|
client_secret: OPID_CLIENT_SECRET,
|
||||||
redirect_uris: [opidRedirectUri],
|
redirect_uris: [OPID_CLIENT_REDIRECT_URL],
|
||||||
response_types: ["code"],
|
response_types: ["code"],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -35,7 +38,7 @@ class OpenIDClient {
|
|||||||
|
|
||||||
public getUserInfo(code: string, nonce: string): Promise<{ email: string; sub: string; access_token: string }> {
|
public getUserInfo(code: string, nonce: string): Promise<{ email: string; sub: string; access_token: string }> {
|
||||||
return this.initClient().then((client) => {
|
return this.initClient().then((client) => {
|
||||||
return client.callback(opidRedirectUri, { code }, { nonce }).then((tokenSet) => {
|
return client.callback(OPID_CLIENT_REDIRECT_URL, { code }, { nonce }).then((tokenSet) => {
|
||||||
return client.userinfo(tokenSet).then((res) => {
|
return client.userinfo(tokenSet).then((res) => {
|
||||||
return {
|
return {
|
||||||
...res,
|
...res,
|
||||||
|
Loading…
Reference in New Issue
Block a user