Active authentication Oauth (#1377)

* Active authentication Oauth

 - Google authentication
 - GitHub authentication
 - Linkedin authentication

Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>

* Finish connexion et get user info connexion

Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>

* Fix lint error

Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>

* Change the expires token for 30 days

Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>

* Update connexion stratgey

 - Set last room when it will be created and not when connexion is openned
 - Add '/login' end point permit to logout and open iframe to log user
 - Add logout feature permit to logout in front

Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>

* Implement logout and revoke token with hydra

Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>

* Fix pull develop conflict

Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>

* Profile url (#1399)

* Create function that permit to get profile URL

Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>

* Continue profil user

Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>

* Add menu and logout button

Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>

* Update last room use

Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>

* Profile callback permit to get url profile setting from admin

Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>

* Finish profile show

Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>

* Delete profileUrl will be not use today

Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>

* Correct lint

Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>

* Update size of iframe

Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>

* Delete console log

Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>

* Update feedback ARP

Signed-off-by: Gregoire Parant <g.parant@thecodingmachine.com>
This commit is contained in:
grégoire parant 2021-09-05 18:17:49 +02:00 committed by GitHub
parent a0d863569b
commit d2b8d7dc04
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 306 additions and 64 deletions

View file

@ -7,6 +7,8 @@ import { localUserStore } from "./LocalUserStore";
import { CharacterTexture, LocalUser } from "./LocalUser";
import { Room } from "./Room";
import { _ServiceWorker } from "../Network/ServiceWorker";
import { loginSceneVisibleIframeStore } from "../Stores/LoginSceneStore";
import { userIsConnected } from "../Stores/MenuStore";
class ConnectionManager {
private localUser!: LocalUser;
@ -15,6 +17,7 @@ class ConnectionManager {
private reconnectingTimeout: NodeJS.Timeout | null = null;
private _unloading: boolean = false;
private authToken: string | null = null;
private _currentRoom: Room | null = null;
private serviceWorker?: _ServiceWorker;
@ -30,28 +33,39 @@ class ConnectionManager {
}
/**
* @return Promise<void>
* TODO fix me to be move in game manager
*/
public loadOpenIDScreen(): Promise<void> {
public loadOpenIDScreen() {
const state = localUserStore.generateState();
const nonce = localUserStore.generateNonce();
localUserStore.setAuthToken(null);
//TODO refactor this and don't realise previous call
return Axios.get(`http://${PUSHER_URL}/login-screen?state=${state}&nonce=${nonce}`)
.then(() => {
window.location.assign(`http://${PUSHER_URL}/login-screen?state=${state}&nonce=${nonce}`);
})
.catch((err) => {
console.error(err, "We don't have URL to regenerate authentication user");
//TODO show modal login
window.location.reload();
});
//TODO fix me to redirect this URL by pusher
if (!this._currentRoom || !this._currentRoom.iframeAuthentication) {
loginSceneVisibleIframeStore.set(false);
return null;
}
const redirectUrl = `${this._currentRoom.iframeAuthentication}?state=${state}&nonce=${nonce}`;
window.location.assign(redirectUrl);
return redirectUrl;
}
public logout() {
/**
* Logout
*/
public async logout() {
//user logout, set connected store for menu at false
userIsConnected.set(false);
//Logout user in pusher and hydra
const token = localUserStore.getAuthToken();
const { authToken } = await Axios.get(`${PUSHER_URL}/logout-callback`, { params: { token } }).then(
(res) => res.data
);
localUserStore.setAuthToken(null);
window.location.reload();
//Go on login page can permit to clear token and start authentication process
window.location.assign("/login");
}
/**
@ -60,8 +74,13 @@ class ConnectionManager {
public async initGameConnexion(): Promise<Room> {
const connexionType = urlManager.getGameConnexionType();
this.connexionType = connexionType;
let room: Room | null = null;
if (connexionType === GameConnexionTypes.jwt) {
this._currentRoom = null;
if (connexionType === GameConnexionTypes.login) {
//TODO clear all cash and redirect on login scene (iframe)
localUserStore.setAuthToken(null);
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
urlManager.pushRoomIdToUrl(this._currentRoom);
} else if (connexionType === GameConnexionTypes.jwt) {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get("code");
const state = urlParams.get("state");
@ -71,14 +90,15 @@ class ConnectionManager {
if (!code) {
throw "No Auth code provided";
}
const nonce = localUserStore.getNonce();
const { authToken } = await Axios.get(`${PUSHER_URL}/login-callback`, { params: { code, nonce } }).then(
(res) => res.data
);
localUserStore.setAuthToken(authToken);
this.authToken = authToken;
room = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
urlManager.pushRoomIdToUrl(room);
localUserStore.setCode(code);
try {
await this.checkAuthUserConnexion();
} catch (err) {
console.error(err);
this.loadOpenIDScreen();
}
this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl()));
urlManager.pushRoomIdToUrl(this._currentRoom);
} else if (connexionType === GameConnexionTypes.register) {
//@deprecated
const organizationMemberToken = urlManager.getOrganizationToken();
@ -92,7 +112,7 @@ class ConnectionManager {
const roomUrl = data.roomUrl;
room = await Room.createRoom(
this._currentRoom = await Room.createRoom(
new URL(
window.location.protocol +
"//" +
@ -102,7 +122,7 @@ class ConnectionManager {
window.location.hash
)
);
urlManager.pushRoomIdToUrl(room);
urlManager.pushRoomIdToUrl(this._currentRoom);
} else if (
connexionType === GameConnexionTypes.organization ||
connexionType === GameConnexionTypes.anonymous ||
@ -112,12 +132,18 @@ class ConnectionManager {
//todo: add here some kind of warning if authToken has expired.
if (!this.authToken) {
await this.anonymousLogin();
} else {
try {
await this.checkAuthUserConnexion();
} catch (err) {
console.error(err);
}
}
this.localUser = localUserStore.getLocalUser() as LocalUser; //if authToken exist in localStorage then localUser cannot be null
let roomPath: string;
if (connexionType === GameConnexionTypes.empty) {
roomPath = window.location.protocol + "//" + window.location.host + START_ROOM_URL;
roomPath = localUserStore.getLastRoomUrl();
//get last room path from cache api
try {
const lastRoomUrl = await localUserStore.getLastRoomUrlCacheApi();
@ -138,13 +164,13 @@ class ConnectionManager {
}
//get detail map for anonymous login and set texture in local storage
room = await Room.createRoom(new URL(roomPath));
if (room.textures != undefined && room.textures.length > 0) {
this._currentRoom = await Room.createRoom(new URL(roomPath));
if (this._currentRoom.textures != undefined && this._currentRoom.textures.length > 0) {
//check if texture was changed
if (this.localUser.textures.length === 0) {
this.localUser.textures = room.textures;
this.localUser.textures = this._currentRoom.textures;
} else {
room.textures.forEach((newTexture) => {
this._currentRoom.textures.forEach((newTexture) => {
const alreadyExistTexture = this.localUser.textures.find((c) => newTexture.id === c.id);
if (this.localUser.textures.findIndex((c) => newTexture.id === c.id) !== -1) {
return;
@ -155,12 +181,12 @@ class ConnectionManager {
localUserStore.saveUser(this.localUser);
}
}
if (room == undefined) {
if (this._currentRoom == undefined) {
return Promise.reject(new Error("Invalid URL"));
}
this.serviceWorker = new _ServiceWorker();
return Promise.resolve(room);
return Promise.resolve(this._currentRoom);
}
public async anonymousLogin(isBenchmark: boolean = false): Promise<void> {
@ -215,9 +241,6 @@ class ConnectionManager {
});
connection.onConnect((connect: OnConnectInterface) => {
//save last room url connected
localUserStore.setLastRoomUrl(roomUrl);
resolve(connect);
});
}).catch((err) => {
@ -237,6 +260,34 @@ class ConnectionManager {
get getConnexionType() {
return this.connexionType;
}
async checkAuthUserConnexion() {
//set connected store for menu at false
userIsConnected.set(false);
const state = localUserStore.getState();
const code = localUserStore.getCode();
if (!state || !localUserStore.verifyState(state)) {
throw "Could not validate state!";
}
if (!code) {
throw "No Auth code provided";
}
const nonce = localUserStore.getNonce();
const token = localUserStore.getAuthToken();
const { authToken } = await Axios.get(`${PUSHER_URL}/login-callback`, { params: { code, nonce, token } }).then(
(res) => res.data
);
localUserStore.setAuthToken(authToken);
this.authToken = authToken;
//user connected, set connected store for menu at true
userIsConnected.set(true);
}
get currentRoom() {
return this._currentRoom;
}
}
export const connectionManager = new ConnectionManager();

View file

@ -1,5 +1,6 @@
import { areCharacterLayersValid, isUserNameValid, LocalUser } from "./LocalUser";
import { v4 as uuidv4 } from "uuid";
import { START_ROOM_URL } from "../Enum/EnvironmentVariable";
const playerNameKey = "playerName";
const selectedPlayerKey = "selectedPlayer";
@ -17,6 +18,7 @@ const authToken = "authToken";
const state = "state";
const nonce = "nonce";
const notification = "notificationPermission";
const code = "code";
const cameraSetup = "cameraSetup";
const cacheAPIIndex = "workavdenture-cache";
@ -126,7 +128,9 @@ class LocalUserStore {
});
}
getLastRoomUrl(): string {
return localStorage.getItem(lastRoomUrl) ?? "";
return (
localStorage.getItem(lastRoomUrl) ?? window.location.protocol + "//" + window.location.host + START_ROOM_URL
);
}
getLastRoomUrlCacheApi(): Promise<string | undefined> {
return caches.open(cacheAPIIndex).then((cache) => {
@ -161,19 +165,24 @@ class LocalUserStore {
verifyState(value: string): boolean {
const oldValue = localStorage.getItem(state);
localStorage.removeItem(state);
return oldValue === value;
}
getState(): string | null {
return localStorage.getItem(state);
}
generateNonce(): string {
const newNonce = uuidv4();
localStorage.setItem(nonce, newNonce);
return newNonce;
}
getNonce(): string | null {
const oldValue = localStorage.getItem(nonce);
localStorage.removeItem(nonce);
return oldValue;
return localStorage.getItem(nonce);
}
setCode(value: string): void {
localStorage.setItem(code, value);
}
getCode(): string | null {
return localStorage.getItem(code);
}
setCameraSetup(cameraId: string) {

View file

@ -14,6 +14,8 @@ export interface RoomRedirect {
export class Room {
public readonly id: string;
public readonly isPublic: boolean;
private _authenticationMandatory: boolean = false;
private _iframeAuthentication?: string;
private _mapUrl: string | undefined;
private _textures: CharacterTexture[] | undefined;
private instance: string | undefined;
@ -101,6 +103,8 @@ export class Room {
console.log("Map ", this.id, " resolves to URL ", data.mapUrl);
this._mapUrl = data.mapUrl;
this._textures = data.textures;
this._authenticationMandatory = data.authenticationMandatory || false;
this._iframeAuthentication = data.iframeAuthentication;
return new MapDetail(data.mapUrl, data.textures);
}
@ -186,4 +190,12 @@ export class Room {
}
return this._mapUrl;
}
get authenticationMandatory(): boolean {
return this._authenticationMandatory;
}
get iframeAuthentication(): string | undefined {
return this._iframeAuthentication;
}
}

View file

@ -78,6 +78,11 @@ export class RoomConnection implements RoomConnection {
*
* @param token A JWT token containing the email of the user
* @param roomUrl The URL of the room in the form "https://example.com/_/[instance]/[map_url]" or "https://example.com/@/[org]/[event]/[map]"
* @param name
* @param characterLayers
* @param position
* @param viewport
* @param companion
*/
public constructor(
token: string | null,
@ -218,7 +223,7 @@ export class RoomConnection implements RoomConnection {
worldFullMessageStream.onMessage();
this.closed = true;
} else if (message.hasTokenexpiredmessage()) {
connectionManager.loadOpenIDScreen();
connectionManager.logout();
this.closed = true; //technically, this isn't needed since loadOpenIDScreen() will do window.location.assign() but I prefer to leave it for consistency
} else if (message.hasWorldconnexionmessage()) {
worldFullMessageStream.onMessage(message.getWorldconnexionmessage()?.getMessage());