Merge branch 'develop' of github.com:thecodingmachine/workadventure into develop

This commit is contained in:
_Bastler
2022-05-05 12:25:09 +02:00
68 changed files with 1375 additions and 280 deletions
+49 -2
View File
@@ -1,9 +1,11 @@
import { BaseHttpController } from "./BaseHttpController";
import * as fs from "fs";
import { ADMIN_URL } from "../Enum/EnvironmentVariable";
import SwaggerGenerator from "../Services/SwaggerGenerator";
export class SwaggerController extends BaseHttpController {
routes() {
this.app.get("/openapi", (req, res) => {
this.app.get("/openapi/pusher", (req, res) => {
// Let's load the module dynamically (it may not exist in prod because part of the -dev packages)
const swaggerJsdoc = require("swagger-jsdoc");
const options = {
@@ -20,6 +22,43 @@ export class SwaggerController extends BaseHttpController {
res.json(swaggerJsdoc(options));
});
this.app.get("/openapi/admin", (req, res) => {
// Let's load the module dynamically (it may not exist in prod because part of the -dev packages)
const swaggerJsdoc = require("swagger-jsdoc");
const options = {
swaggerDefinition: {
swagger: "2.0",
//openapi: "3.0.0",
info: {
title: "WorkAdventure Pusher",
version: "1.0.0",
description:
"This is a documentation about the endpoints called by the pusher. \n You can find out more about WorkAdventure on [github](https://github.com/thecodingmachine/workadventure).",
contact: {
email: "hello@workadventu.re",
},
},
host: "pusher." + ADMIN_URL.replace("//", ""),
tags: [
{
name: "AdminAPI",
description: "Access to end points of the admin from the pusher",
},
],
securityDefinitions: {
Bearer: {
type: "apiKey",
name: "Authorization",
in: "header",
},
},
...SwaggerGenerator.definitions(),
},
apis: ["./src/Services/*.ts"],
};
res.json(swaggerJsdoc(options));
});
// Create a LiveDirectory instance to virtualize directory with our assets
// @ts-ignore
const LiveDirectory = require("live-directory");
@@ -39,8 +78,16 @@ export class SwaggerController extends BaseHttpController {
if (err) {
return response.status(500).send(err.message);
}
const result = data.replace(/https:\/\/petstore\.swagger\.io\/v2\/swagger.json/g, "/openapi");
const urls = [
{ url: "/openapi/pusher", name: "Front -> Pusher" },
{ url: "/openapi/admin", name: "Pusher <- Admin" },
];
const result = data.replace(
/url: "https:\/\/petstore\.swagger\.io\/v2\/swagger.json"/g,
`urls: ${JSON.stringify(urls)}, "urls.primaryName": "Pusher <- Admin"`
);
response.send(result);
return;
@@ -1,4 +1,5 @@
import { z } from "zod";
import { extendApi } from "@anatine/zod-openapi";
export const isBanBannedAdminMessageInterface = z.object({
type: z.enum(["ban", "banned"]),
@@ -8,7 +9,7 @@ export const isBanBannedAdminMessageInterface = z.object({
export const isUserMessageAdminMessageInterface = z.object({
event: z.enum(["user-message"]),
message: isBanBannedAdminMessageInterface,
message: extendApi(isBanBannedAdminMessageInterface, { $ref: "#/definitions/BanBannedAdminMessageInterface" }),
world: z.string(),
jwt: z.string(),
});
+267 -12
View File
@@ -9,6 +9,7 @@ import qs from "qs";
import { AdminInterface } from "./AdminInterface";
import { AuthTokenData, jwtTokenManager } from "./JWTTokenManager";
import { InvalidTokenError } from "../Controller/InvalidTokenError";
import { extendApi } from "@anatine/zod-openapi";
export interface AdminBannedData {
is_banned: boolean;
@@ -16,15 +17,31 @@ export interface AdminBannedData {
}
export const isFetchMemberDataByUuidResponse = z.object({
email: z.string(),
userUuid: z.string(),
tags: z.array(z.string()),
visitCardUrl: z.nullable(z.string()),
textures: z.array(isWokaDetail),
messages: z.array(z.unknown()),
// @ts-ignore
email: extendApi(z.string(), {
description: "The email of the fetched user, it can be an email, an uuid or undefined.",
example: "example@workadventu.re",
}),
userUuid: extendApi(z.string(), {
description: "The uuid of the fetched user, it can be an email, an uuid or undefined.",
example: "998ce839-3dea-4698-8b41-ebbdf7688ad9",
}),
tags: extendApi(z.array(z.string()), {
description: "List of tags related to the user fetched.",
example: ["editor"],
}),
visitCardUrl: extendApi(z.nullable(z.string()), {
description: "URL of the visitCard of the user fetched.",
example: "https://mycompany.com/contact/me",
}),
textures: extendApi(z.array(isWokaDetail), { $ref: "#/definitions/WokaDetail" }),
messages: extendApi(z.array(z.unknown()), { description: "List of user's messages." }),
anonymous: z.optional(z.boolean()),
userRoomToken: z.optional(z.string()),
anonymous: extendApi(z.optional(z.boolean()), {
description: "Whether the user if logged as anonymous or not",
example: false,
}),
userRoomToken: extendApi(z.optional(z.string()), { description: "", example: "" }),
});
export type FetchMemberDataByUuidResponse = z.infer<typeof isFetchMemberDataByUuidResponse>;
@@ -69,6 +86,47 @@ class AdminApi implements AdminInterface {
userId,
};
/**
* @openapi
* /api/map:
* get:
* tags: ["AdminAPI"]
* description: Returns a map mapping map name to file name of the map
* security:
* - Bearer: []
* produces:
* - "application/json"
* parameters:
* - name: "playUri"
* in: "query"
* description: "The full URL of WorkAdventure"
* required: true
* type: "string"
* example: "http://play.workadventure.localhost/@/teamSlug/worldSLug/roomSlug"
* - name: "userId"
* in: "query"
* description: "The identifier of the current user \n It can be undefined or an uuid or an email"
* type: "string"
* example: "998ce839-3dea-4698-8b41-ebbdf7688ad9"
* responses:
* 200:
* description: The details of the member
* schema:
* $ref: "#/definitions/MapDetailsData"
* 401:
* description: Error while retrieving the data because you are not authorized
* schema:
* $ref: '#/definitions/ErrorApiRedirectData'
* 403:
* description: Error while retrieving the data because you are not authorized
* schema:
* $ref: '#/definitions/ErrorApiUnauthorizedData'
* 404:
* description: Error while retrieving the data
* schema:
* $ref: '#/definitions/ErrorApiErrorData'
*
*/
const res = await Axios.get<unknown, AxiosResponse<unknown>>(ADMIN_API_URL + "/api/map", {
headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" },
params,
@@ -99,6 +157,58 @@ class AdminApi implements AdminInterface {
characterLayers: string[],
locale?: string
): Promise<FetchMemberDataByUuidResponse> {
/**
* @openapi
* /api/room/access:
* get:
* tags: ["AdminAPI"]
* description: Returns member's informations if he can access this room
* security:
* - Bearer: []
* produces:
* - "application/json"
* parameters:
* - name: "userIdentifier"
* in: "query"
* description: "The identifier of the current user \n It can be undefined or an uuid or an email"
* type: "string"
* example: "998ce839-3dea-4698-8b41-ebbdf7688ad9"
* - name: "playUri"
* in: "query"
* description: "The full URL of WorkAdventure"
* required: true
* type: "string"
* example: "http://play.workadventure.localhost/@/teamSlug/worldSLug/roomSlug"
* - name: "ipAddress"
* in: "query"
* required: true
* type: "string"
* example: "127.0.0.1"
* - name: "characterLayers"
* in: "query"
* type: "array"
* items:
* type: string
* example: ["male1"]
* responses:
* 200:
* description: The details of the member
* schema:
* $ref: "#/definitions/FetchMemberDataByUuidResponse"
* 401:
* description: Error while retrieving the data because you are not authorized
* schema:
* $ref: '#/definitions/ErrorApiRedirectData'
* 403:
* description: Error while retrieving the data because you are not authorized
* schema:
* $ref: '#/definitions/ErrorApiUnauthorizedData'
* 404:
* description: Error while retrieving the data
* schema:
* $ref: '#/definitions/ErrorApiErrorData'
*
*/
const res = await Axios.get<unknown, AxiosResponse<unknown>>(ADMIN_API_URL + "/api/room/access", {
params: {
userIdentifier,
@@ -130,6 +240,42 @@ class AdminApi implements AdminInterface {
playUri: string | null,
locale?: string
): Promise<AdminApiData> {
/**
* @openapi
* /api/login-url/{organizationMemberToken}:
* get:
* tags: ["AdminAPI"]
* description: Returns a member from the token
* security:
* - Bearer: []
* produces:
* - "application/json"
* parameters:
* - name: "organizationMemberToken"
* in: "path"
* description: "The token of member in the organization"
* type: "string"
* - name: "playUri"
* in: "query"
* description: "The full URL of WorkAdventure"
* required: true
* type: "string"
* example: "http://play.workadventure.localhost/@/teamSlug/worldSLug/roomSlug"
* responses:
* 200:
* description: The details of the member
* schema:
* $ref: "#/definitions/AdminApiData"
* 401:
* description: Error while retrieving the data because you are not authorized
* schema:
* $ref: '#/definitions/ErrorApiRedirectData'
* 404:
* description: Error while retrieving the data
* schema:
* $ref: '#/definitions/ErrorApiErrorData'
*
*/
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
const res = await Axios.get(ADMIN_API_URL + "/api/login-url/" + organizationMemberToken, {
params: { playUri },
@@ -154,6 +300,41 @@ class AdminApi implements AdminInterface {
reportWorldSlug: string,
locale?: string
) {
/**
* @openapi
* /api/report:
* post:
* tags: ["AdminAPI"]
* description: Report one user with a comment
* security:
* - Bearer: []
* produces:
* - "application/json"
* parameters:
* - name: "reportedUserUuid"
* in: "query"
* description: "The identifier of the reported user \n It can be an uuid or an email"
* type: "string"
* example: "998ce839-3dea-4698-8b41-ebbdf7688ad9"
* - name: "reportedUserComment"
* in: "query"
* description: "The comment of the report"
* required: true
* type: "string"
* - name: "reporterUserUuid"
* in: "query"
* description: "The identifier of the reporter user \n It can be an uuid or an email"
* type: "string"
* example: "998ce839-3dea-4698-8b41-ebbdf7688ad8"
* - name: "reportWorldSlug"
* in: "query"
* description: "The slug of the world where the report is made"
* type: "string"
* example: "/@/teamSlug/worldSlug/roomSlug"
* responses:
* 200:
* description: The report has been successfully saved
*/
return Axios.post(
`${ADMIN_API_URL}/api/report`,
{
@@ -174,6 +355,53 @@ class AdminApi implements AdminInterface {
roomUrl: string,
locale?: string
): Promise<AdminBannedData> {
/**
* @openapi
* /api/ban:
* get:
* tags: ["AdminAPI"]
* description: Check if user is banned or not
* security:
* - Bearer: []
* produces:
* - "application/json"
* parameters:
* - name: "ipAddress"
* in: "query"
* type: "string"
* required: true
* example: "127.0.0.1"
* - name: "token"
* in: "query"
* description: "The uuid of the user \n It can be an uuid or an email"
* type: "string"
* required: true
* example: "998ce839-3dea-4698-8b41-ebbdf7688ad8"
* - name: "roomUrl"
* in: "query"
* description: "The slug of the world where to check if the user is banned"
* type: "string"
* required: true
* example: "/@/teamSlug/worldSlug/roomSlug"
* responses:
* 200:
* description: The user is banned or not
* content:
* application/json:
* schema:
* type: array
* required:
* - is_banned
* properties:
* is_banned:
* type: boolean
* description: Whether the user is banned or not
* example: true
* 404:
* description: Error while retrieving the data
* schema:
* $ref: '#/definitions/ErrorApiErrorData'
*/
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
return Axios.get(
ADMIN_API_URL +
@@ -191,6 +419,37 @@ class AdminApi implements AdminInterface {
}
async getUrlRoomsFromSameWorld(roomUrl: string, locale?: string): Promise<string[]> {
/**
* @openapi
* /api/room/sameWorld:
* get:
* tags: ["AdminAPI"]
* description: Get all URLs of the rooms from the world specified
* security:
* - Bearer: []
* produces:
* - "application/json"
* parameters:
* - name: "roomUrl"
* in: "query"
* description: "The slug of the room"
* type: "string"
* required: true
* example: "/@/teamSlug/worldSlug/roomSlug"
* responses:
* 200:
* description: The list of URL of the rooms from the same world
* schema:
* type: array
* items:
* type: string
* description: URL of a room
* example: "http://example.com/@/teamSlug/worldSlug/room2Slug"
* 404:
* description: Error while retrieving the data
* schema:
* $ref: '#/definitions/ErrorApiErrorData'
*/
return Axios.get(ADMIN_API_URL + "/api/room/sameWorld" + "?roomUrl=" + encodeURIComponent(roomUrl), {
headers: { Authorization: `${ADMIN_API_TOKEN}`, "Accept-Language": locale ?? "en" },
}).then((data) => {
@@ -204,10 +463,6 @@ class AdminApi implements AdminInterface {
}
return `${OPID_PROFILE_SCREEN_PROVIDER}?accessToken=${accessToken}`;
}
async logoutOauth(token: string): Promise<void> {
await Axios.get(ADMIN_API_URL + `/oauth/logout?token=${token}`);
}
}
export const adminApi = new AdminApi();
-5
View File
@@ -74,9 +74,4 @@ export interface AdminInterface {
* @return string
*/
getProfileUrl(accessToken: string): string;
/**
* @param token
*/
logoutOauth(token: string): Promise<void>;
}
-4
View File
@@ -87,10 +87,6 @@ class LocalAdmin implements AdminInterface {
new Error("No admin backoffice set!");
return "";
}
async logoutOauth(token: string): Promise<void> {
return Promise.reject(new Error("No admin backoffice set!"));
}
}
export const localAdmin = new LocalAdmin();
+1 -1
View File
@@ -658,7 +658,7 @@ export class SocketManager implements ZoneEventListener {
public emitErrorScreenMessage(client: compressors.WebSocket, errorApi: ErrorApiData) {
const errorMessage = new ErrorScreenMessage();
errorMessage.setType(errorApi.type);
if (errorApi.type == "retry" || errorApi.type == "error") {
if (errorApi.type == "retry" || errorApi.type == "error" || errorApi.type == "unauthorized") {
errorMessage.setCode(new StringValue().setValue(errorApi.code));
errorMessage.setTitle(new StringValue().setValue(errorApi.title));
errorMessage.setSubtitle(new StringValue().setValue(errorApi.subtitle));
+35
View File
@@ -0,0 +1,35 @@
import { generateSchema } from "@anatine/zod-openapi";
import { isAdminApiData } from "../Messages/JsonMessages/AdminApiData";
import {
isErrorApiErrorData,
isErrorApiRedirectData,
isErrorApiRetryData,
isErrorApiUnauthorizedData,
} from "../Messages/JsonMessages/ErrorApiData";
import { isMapDetailsData } from "../Messages/JsonMessages/MapDetailsData";
import { isFetchMemberDataByUuidResponse } from "./AdminApi";
import { isWokaDetail } from "../Messages/JsonMessages/PlayerTextures";
class SwaggerGenerator {
definitions() {
return {
definitions: {
AdminApiData: generateSchema(isAdminApiData),
//BanBannedAdminMessageInterface: generateSchema(isBanBannedAdminMessageInterface),
ErrorApiErrorData: generateSchema(isErrorApiErrorData),
ErrorApiRedirectData: generateSchema(isErrorApiRedirectData),
ErrorApiRetryData: generateSchema(isErrorApiRetryData),
ErrorApiUnauthorizedData: generateSchema(isErrorApiUnauthorizedData),
FetchMemberDataByUuidResponse: generateSchema(isFetchMemberDataByUuidResponse),
//ListenRoomsMessageInterface: generateSchema(isListenRoomsMessageInterface),
MapDetailsData: generateSchema(isMapDetailsData),
//RegisterData: generateSchema(isRegisterData),
//RoomRedirect: generateSchema(isRoomRedirect),
//UserMessageAdminMessageInterface: generateSchema(isUserMessageAdminMessageInterface),
WokaDetail: generateSchema(isWokaDetail),
},
};
}
}
export default new SwaggerGenerator();