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:
parent
a0d863569b
commit
d2b8d7dc04
19 changed files with 306 additions and 64 deletions
|
@ -2,7 +2,7 @@ import { v4 } from "uuid";
|
|||
import { HttpRequest, HttpResponse, TemplatedApp } from "uWebSockets.js";
|
||||
import { BaseController } from "./BaseController";
|
||||
import { adminApi } from "../Services/AdminApi";
|
||||
import { jwtTokenManager } from "../Services/JWTTokenManager";
|
||||
import { AuthTokenData, jwtTokenManager } from "../Services/JWTTokenManager";
|
||||
import { parse } from "query-string";
|
||||
import { openIDClient } from "../Services/OpenIDClient";
|
||||
|
||||
|
@ -17,6 +17,7 @@ export class AuthenticateController extends BaseController {
|
|||
this.openIDCallback();
|
||||
this.register();
|
||||
this.anonymLogin();
|
||||
this.profileCallback();
|
||||
}
|
||||
|
||||
openIDLogin() {
|
||||
|
@ -48,14 +49,31 @@ export class AuthenticateController extends BaseController {
|
|||
res.onAborted(() => {
|
||||
console.warn("/message request was aborted");
|
||||
});
|
||||
const { code, nonce } = parse(req.getQuery());
|
||||
const { code, nonce, token } = parse(req.getQuery());
|
||||
try {
|
||||
//verify connected by token
|
||||
if (token != undefined) {
|
||||
try {
|
||||
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
||||
if (authTokenData.hydraAccessToken == undefined) {
|
||||
throw Error("Token cannot to be check on Hydra");
|
||||
}
|
||||
await openIDClient.checkTokenAuth(authTokenData.hydraAccessToken);
|
||||
res.writeStatus("200");
|
||||
this.addCorsHeaders(res);
|
||||
return res.end(JSON.stringify({ authToken: token }));
|
||||
} catch (err) {
|
||||
console.info("User was not connected", err);
|
||||
}
|
||||
}
|
||||
|
||||
//user have not token created, check data on hydra and create token
|
||||
const userInfo = await openIDClient.getUserInfo(code as string, nonce as string);
|
||||
const email = userInfo.email || userInfo.sub;
|
||||
if (!email) {
|
||||
throw new Error("No email in the response");
|
||||
}
|
||||
const authToken = jwtTokenManager.createAuthToken(email);
|
||||
const authToken = jwtTokenManager.createAuthToken(email, userInfo.access_token);
|
||||
res.writeStatus("200");
|
||||
this.addCorsHeaders(res);
|
||||
return res.end(JSON.stringify({ authToken }));
|
||||
|
@ -63,6 +81,30 @@ export class AuthenticateController extends BaseController {
|
|||
return this.errorToResponse(e, res);
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
this.App.get("/logout-callback", async (res: HttpResponse, req: HttpRequest) => {
|
||||
res.onAborted(() => {
|
||||
console.warn("/message request was aborted");
|
||||
});
|
||||
|
||||
const { token } = parse(req.getQuery());
|
||||
|
||||
try {
|
||||
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
||||
if (authTokenData.hydraAccessToken == undefined) {
|
||||
throw Error("Token cannot to be logout on Hydra");
|
||||
}
|
||||
await openIDClient.logoutUser(authTokenData.hydraAccessToken);
|
||||
} catch (error) {
|
||||
console.error("openIDCallback => logout-callback", error);
|
||||
} finally {
|
||||
res.writeStatus("200");
|
||||
this.addCorsHeaders(res);
|
||||
// eslint-disable-next-line no-unsafe-finally
|
||||
return res.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Try to login with an admin token
|
||||
|
@ -136,4 +178,39 @@ export class AuthenticateController extends BaseController {
|
|||
);
|
||||
});
|
||||
}
|
||||
|
||||
profileCallback() {
|
||||
//eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
this.App.get("/profile-callback", async (res: HttpResponse, req: HttpRequest) => {
|
||||
res.onAborted(() => {
|
||||
console.warn("/message request was aborted");
|
||||
});
|
||||
const { userIdentify, token } = parse(req.getQuery());
|
||||
try {
|
||||
//verify connected by token
|
||||
if (token != undefined) {
|
||||
try {
|
||||
const authTokenData: AuthTokenData = jwtTokenManager.verifyJWTToken(token as string, false);
|
||||
if (authTokenData.hydraAccessToken == undefined) {
|
||||
throw Error("Token cannot to be check on Hydra");
|
||||
}
|
||||
await openIDClient.checkTokenAuth(authTokenData.hydraAccessToken);
|
||||
|
||||
//get login profile
|
||||
res.writeStatus("302");
|
||||
res.writeHeader("Location", adminApi.getProfileUrl(authTokenData.hydraAccessToken));
|
||||
this.addCorsHeaders(res);
|
||||
// eslint-disable-next-line no-unsafe-finally
|
||||
return res.end();
|
||||
} catch (error) {
|
||||
return this.errorToResponse(error, res);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.errorToResponse(error, res);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ const SECRET_KEY = process.env.SECRET_KEY || "THECODINGMACHINE_SECRET_KEY";
|
|||
const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == "true" : false;
|
||||
const API_URL = process.env.API_URL || "";
|
||||
const ADMIN_API_URL = process.env.ADMIN_API_URL || "";
|
||||
const ADMIN_URL = process.env.ADMIN_URL || "";
|
||||
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || "myapitoken";
|
||||
const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80;
|
||||
const JITSI_URL: string | undefined = process.env.JITSI_URL === "" ? undefined : process.env.JITSI_URL;
|
||||
|
@ -19,6 +20,7 @@ export {
|
|||
SECRET_KEY,
|
||||
API_URL,
|
||||
ADMIN_API_URL,
|
||||
ADMIN_URL,
|
||||
ADMIN_API_TOKEN,
|
||||
ALLOW_ARTILLERY,
|
||||
CPU_OVERHEAT_THRESHOLD,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ADMIN_API_TOKEN, ADMIN_API_URL } from "../Enum/EnvironmentVariable";
|
||||
import { ADMIN_API_TOKEN, ADMIN_API_URL, ADMIN_URL } from "../Enum/EnvironmentVariable";
|
||||
import Axios from "axios";
|
||||
import { GameRoomPolicyTypes } from "_Model/PusherRoom";
|
||||
import { CharacterTexture } from "./AdminApi/CharacterTexture";
|
||||
|
@ -141,6 +141,15 @@ class AdminApi {
|
|||
return data.data;
|
||||
});
|
||||
}
|
||||
|
||||
/*TODO add constant to use profile companny*/
|
||||
getProfileUrl(accessToken: string): string {
|
||||
if (!ADMIN_URL) {
|
||||
throw new Error("No admin backoffice set!");
|
||||
}
|
||||
|
||||
return ADMIN_URL + `/profile?token=${accessToken}`;
|
||||
}
|
||||
}
|
||||
|
||||
export const adminApi = new AdminApi();
|
||||
|
|
|
@ -6,13 +6,13 @@ import { adminApi, AdminBannedData } from "../Services/AdminApi";
|
|||
|
||||
export interface AuthTokenData {
|
||||
identifier: string; //will be a email if logged in or an uuid if anonymous
|
||||
hydraAccessToken?: string;
|
||||
}
|
||||
export const tokenInvalidException = "tokenInvalid";
|
||||
|
||||
class JWTTokenManager {
|
||||
public createAuthToken(identifier: string) {
|
||||
//TODO fix me 200d when ory authentication will be available
|
||||
return Jwt.sign({ identifier }, SECRET_KEY, { expiresIn: "200d" });
|
||||
public createAuthToken(identifier: string, hydraAccessToken?: string) {
|
||||
return Jwt.sign({ identifier, hydraAccessToken }, SECRET_KEY, { expiresIn: "30d" });
|
||||
}
|
||||
|
||||
public verifyJWTToken(token: string, ignoreExpiration: boolean = false): AuthTokenData {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Issuer, Client } from "openid-client";
|
||||
import { Issuer, Client, IntrospectionResponse } from "openid-client";
|
||||
import { OPID_CLIENT_ID, OPID_CLIENT_SECRET, OPID_CLIENT_ISSUER, FRONT_URL } from "../Enum/EnvironmentVariable";
|
||||
|
||||
const opidRedirectUri = FRONT_URL + "/jwt";
|
||||
|
@ -31,13 +31,32 @@ class OpenIDClient {
|
|||
});
|
||||
}
|
||||
|
||||
public getUserInfo(code: string, nonce: string): Promise<{ email: string; sub: string }> {
|
||||
public getUserInfo(code: string, nonce: string): Promise<{ email: string; sub: string; access_token: string }> {
|
||||
return this.initClient().then((client) => {
|
||||
return client.callback(opidRedirectUri, { code }, { nonce }).then((tokenSet) => {
|
||||
return client.userinfo(tokenSet);
|
||||
return client.userinfo(tokenSet).then((res) => {
|
||||
return {
|
||||
...res,
|
||||
email: res.email as string,
|
||||
sub: res.sub,
|
||||
access_token: tokenSet.access_token as string,
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public logoutUser(token: string): Promise<void> {
|
||||
return this.initClient().then((client) => {
|
||||
return client.revoke(token);
|
||||
});
|
||||
}
|
||||
|
||||
public checkTokenAuth(token: string): Promise<IntrospectionResponse> {
|
||||
return this.initClient().then((client) => {
|
||||
return client.userinfo(token);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const openIDClient = new OpenIDClient();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue